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
|
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,
|
// ConfirmationEvent encapsulates a confirmation notification. With this struct,
|
||||||
// callers can be notified of: the instance the target txid reaches the targeted
|
// 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
|
// 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
|
// Updates is a channel that will sent upon, at every incremental
|
||||||
// confirmation, how many confirmations are left to declare the
|
// 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
|
// NOTE: This channel must be buffered with the number of required
|
||||||
// confirmations.
|
// confirmations.
|
||||||
Updates chan uint32
|
Updates chan TxUpdateInfo
|
||||||
|
|
||||||
// NegativeConf is a channel that will be sent upon if the transaction
|
// NegativeConf is a channel that will be sent upon if the transaction
|
||||||
// confirms, but is later reorged out of the chain. The integer sent
|
// 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
|
// the channel so we need to create a larger buffer to avoid
|
||||||
// blocking the notifier.
|
// blocking the notifier.
|
||||||
Confirmed: make(chan *TxConfirmation, 1),
|
Confirmed: make(chan *TxConfirmation, 1),
|
||||||
Updates: make(chan uint32, numConfs),
|
Updates: make(chan TxUpdateInfo, numConfs),
|
||||||
NegativeConf: make(chan int32, 1),
|
NegativeConf: make(chan int32, 1),
|
||||||
Done: make(chan struct{}, 1),
|
Done: make(chan struct{}, 1),
|
||||||
Cancel: cancel,
|
Cancel: cancel,
|
||||||
|
|||||||
+42
-12
@@ -947,8 +947,12 @@ func (n *TxNotifier) dispatchConfDetails(
|
|||||||
|
|
||||||
// We'll send a 0 value to the Updates channel,
|
// We'll send a 0 value to the Updates channel,
|
||||||
// indicating that the transaction/output script has already
|
// indicating that the transaction/output script has already
|
||||||
// been confirmed.
|
// been confirmed, and include the block height at which the
|
||||||
err := n.notifyNumConfsLeft(ntfn, 0)
|
// transaction was included.
|
||||||
|
err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
|
||||||
|
NumConfsLeft: 0,
|
||||||
|
BlockHeight: details.BlockHeight,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -977,7 +981,10 @@ func (n *TxNotifier) dispatchConfDetails(
|
|||||||
// confirmations are left for the transaction/output script to
|
// confirmations are left for the transaction/output script to
|
||||||
// be confirmed.
|
// be confirmed.
|
||||||
numConfsLeft := confHeight - n.currentHeight
|
numConfsLeft := confHeight - n.currentHeight
|
||||||
err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
|
err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
|
||||||
|
NumConfsLeft: numConfsLeft,
|
||||||
|
BlockHeight: details.BlockHeight,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1731,7 +1738,10 @@ func (n *TxNotifier) NotifyHeight(height uint32) error {
|
|||||||
for confRequest := range confRequests {
|
for confRequest := range confRequests {
|
||||||
confSet := n.confNotifications[confRequest]
|
confSet := n.confNotifications[confRequest]
|
||||||
for _, ntfn := range confSet.ntfns {
|
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
|
ntfn.NumConfirmations - 1
|
||||||
numConfsLeft := txConfHeight - height
|
numConfsLeft := txConfHeight - height
|
||||||
|
|
||||||
@@ -1744,7 +1754,10 @@ func (n *TxNotifier) NotifyHeight(height uint32) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err := n.notifyNumConfsLeft(ntfn, numConfsLeft)
|
err := n.notifyNumConfsLeft(ntfn, TxUpdateInfo{
|
||||||
|
NumConfsLeft: numConfsLeft,
|
||||||
|
BlockHeight: blockHeight,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -2011,6 +2024,20 @@ func (n *TxNotifier) dispatchConfReorg(ntfn *ConfNtfn,
|
|||||||
if !ntfn.dispatched {
|
if !ntfn.dispatched {
|
||||||
confHeight := heightDisconnected + ntfn.NumConfirmations - 1
|
confHeight := heightDisconnected + ntfn.NumConfirmations - 1
|
||||||
ntfnSet, exists := n.ntfnsByConfirmHeight[confHeight]
|
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 {
|
if exists {
|
||||||
delete(ntfnSet, ntfn)
|
delete(ntfnSet, ntfn)
|
||||||
}
|
}
|
||||||
@@ -2099,25 +2126,28 @@ func (n *TxNotifier) TearDown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// notifyNumConfsLeft sends the number of confirmations left to the
|
// 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.
|
// 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
|
// 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.
|
// 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 "+
|
Log.Debugf("Skipped dispatched update (numConfsLeft=%v) for "+
|
||||||
"request %v conf_id=%v", num, ntfn.ConfRequest,
|
"request %v conf_id=%v", info.NumConfsLeft,
|
||||||
ntfn.ConfID)
|
ntfn.ConfRequest, ntfn.ConfID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the number of confirmations left to the notification.
|
// Update the number of confirmations left to the notification.
|
||||||
ntfn.numConfsLeft = num
|
ntfn.numConfsLeft = info.NumConfsLeft
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case ntfn.Event.Updates <- num:
|
case ntfn.Event.Updates <- info:
|
||||||
case <-n.quit:
|
case <-n.quit:
|
||||||
return ErrTxNotifierExiting
|
return ErrTxNotifierExiting
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,13 +271,12 @@ func TestTxNotifierFutureConfDispatch(t *testing.T) {
|
|||||||
// We should only receive one update for tx1 since it only requires
|
// We should only receive one update for tx1 since it only requires
|
||||||
// one confirmation and it already met it.
|
// one confirmation and it already met it.
|
||||||
select {
|
select {
|
||||||
case numConfsLeft := <-ntfn1.Event.Updates:
|
case updDetails := <-ntfn1.Event.Updates:
|
||||||
const expected = 0
|
expected := chainntnfs.TxUpdateInfo{
|
||||||
if numConfsLeft != expected {
|
NumConfsLeft: 0,
|
||||||
t.Fatalf("Received incorrect confirmation update: tx1 "+
|
BlockHeight: 11,
|
||||||
"expected %d confirmations left, got %d",
|
|
||||||
expected, numConfsLeft)
|
|
||||||
}
|
}
|
||||||
|
require.Equal(t, expected, updDetails)
|
||||||
default:
|
default:
|
||||||
t.Fatal("Expected confirmation update for tx1")
|
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
|
// We should only receive one update for tx2 since it only has one
|
||||||
// confirmation so far and it requires two.
|
// confirmation so far and it requires two.
|
||||||
select {
|
select {
|
||||||
case numConfsLeft := <-ntfn2.Event.Updates:
|
case updDetails := <-ntfn2.Event.Updates:
|
||||||
const expected = 1
|
expected := chainntnfs.TxUpdateInfo{
|
||||||
if numConfsLeft != expected {
|
NumConfsLeft: 1,
|
||||||
t.Fatalf("Received incorrect confirmation update: tx2 "+
|
BlockHeight: 11,
|
||||||
"expected %d confirmations left, got %d",
|
|
||||||
expected, numConfsLeft)
|
|
||||||
}
|
}
|
||||||
|
require.Equal(t, expected, updDetails)
|
||||||
default:
|
default:
|
||||||
t.Fatal("Expected confirmation update for tx2")
|
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,
|
// We should only receive one update since the last at the new height,
|
||||||
// indicating how many confirmations are still left.
|
// indicating how many confirmations are still left.
|
||||||
select {
|
select {
|
||||||
case numConfsLeft := <-ntfn2.Event.Updates:
|
case updDetails := <-ntfn2.Event.Updates:
|
||||||
const expected = 0
|
expected := chainntnfs.TxUpdateInfo{
|
||||||
if numConfsLeft != expected {
|
NumConfsLeft: 0,
|
||||||
t.Fatalf("Received incorrect confirmation update: tx2 "+
|
BlockHeight: 11,
|
||||||
"expected %d confirmations left, got %d",
|
|
||||||
expected, numConfsLeft)
|
|
||||||
}
|
}
|
||||||
|
require.Equal(t, expected, updDetails)
|
||||||
default:
|
default:
|
||||||
t.Fatal("Expected confirmation update for tx2")
|
t.Fatal("Expected confirmation update for tx2")
|
||||||
}
|
}
|
||||||
@@ -411,13 +408,12 @@ func TestTxNotifierHistoricalConfDispatch(t *testing.T) {
|
|||||||
err = n.UpdateConfDetails(ntfn1.HistoricalDispatch.ConfRequest, &txConf1)
|
err = n.UpdateConfDetails(ntfn1.HistoricalDispatch.ConfRequest, &txConf1)
|
||||||
require.NoError(t, err, "unable to update conf details")
|
require.NoError(t, err, "unable to update conf details")
|
||||||
select {
|
select {
|
||||||
case numConfsLeft := <-ntfn1.Event.Updates:
|
case updDetails := <-ntfn1.Event.Updates:
|
||||||
const expected = 0
|
expected := chainntnfs.TxUpdateInfo{
|
||||||
if numConfsLeft != expected {
|
NumConfsLeft: 0,
|
||||||
t.Fatalf("Received incorrect confirmation update: tx1 "+
|
BlockHeight: 9,
|
||||||
"expected %d confirmations left, got %d",
|
|
||||||
expected, numConfsLeft)
|
|
||||||
}
|
}
|
||||||
|
require.Equal(t, expected, updDetails)
|
||||||
default:
|
default:
|
||||||
t.Fatal("Expected confirmation update for tx1")
|
t.Fatal("Expected confirmation update for tx1")
|
||||||
}
|
}
|
||||||
@@ -443,13 +439,12 @@ func TestTxNotifierHistoricalConfDispatch(t *testing.T) {
|
|||||||
err = n.UpdateConfDetails(ntfn2.HistoricalDispatch.ConfRequest, &txConf2)
|
err = n.UpdateConfDetails(ntfn2.HistoricalDispatch.ConfRequest, &txConf2)
|
||||||
require.NoError(t, err, "unable to update conf details")
|
require.NoError(t, err, "unable to update conf details")
|
||||||
select {
|
select {
|
||||||
case numConfsLeft := <-ntfn2.Event.Updates:
|
case updDetails := <-ntfn2.Event.Updates:
|
||||||
const expected = 1
|
expected := chainntnfs.TxUpdateInfo{
|
||||||
if numConfsLeft != expected {
|
NumConfsLeft: 1,
|
||||||
t.Fatalf("Received incorrect confirmation update: tx2 "+
|
BlockHeight: 9,
|
||||||
"expected %d confirmations left, got %d",
|
|
||||||
expected, numConfsLeft)
|
|
||||||
}
|
}
|
||||||
|
require.Equal(t, expected, updDetails)
|
||||||
default:
|
default:
|
||||||
t.Fatal("Expected confirmation update for tx2")
|
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,
|
// We should only receive one update for tx2 since the last one,
|
||||||
// indicating how many confirmations are still left.
|
// indicating how many confirmations are still left.
|
||||||
select {
|
select {
|
||||||
case numConfsLeft := <-ntfn2.Event.Updates:
|
case updDetails := <-ntfn2.Event.Updates:
|
||||||
const expected = 0
|
expected := chainntnfs.TxUpdateInfo{
|
||||||
if numConfsLeft != expected {
|
NumConfsLeft: 0,
|
||||||
t.Fatalf("Received incorrect confirmation update: tx2 "+
|
BlockHeight: 9,
|
||||||
"expected %d confirmations left, got %d",
|
|
||||||
expected, numConfsLeft)
|
|
||||||
}
|
}
|
||||||
|
require.Equal(t, expected, updDetails)
|
||||||
default:
|
default:
|
||||||
t.Fatal("Expected confirmation update for tx2")
|
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
|
// We should only receive one update for tx2 since it only requires
|
||||||
// one confirmation and it already met it.
|
// one confirmation and it already met it.
|
||||||
select {
|
select {
|
||||||
case numConfsLeft := <-ntfn2.Event.Updates:
|
case updDetails := <-ntfn2.Event.Updates:
|
||||||
const expected = 0
|
expected := chainntnfs.TxUpdateInfo{
|
||||||
if numConfsLeft != expected {
|
NumConfsLeft: 0,
|
||||||
t.Fatalf("Received incorrect confirmation update: tx2 "+
|
BlockHeight: 12,
|
||||||
"expected %d confirmations left, got %d",
|
|
||||||
expected, numConfsLeft)
|
|
||||||
}
|
}
|
||||||
|
require.Equal(t, expected, updDetails)
|
||||||
default:
|
default:
|
||||||
t.Fatal("Expected confirmation update for tx2")
|
t.Fatal("Expected confirmation update for tx2")
|
||||||
}
|
}
|
||||||
@@ -1520,15 +1513,14 @@ func TestTxNotifierConfReorg(t *testing.T) {
|
|||||||
// confirmations and it has already met them.
|
// confirmations and it has already met them.
|
||||||
for i := uint32(1); i <= 2; i++ {
|
for i := uint32(1); i <= 2; i++ {
|
||||||
select {
|
select {
|
||||||
case numConfsLeft := <-ntfn3.Event.Updates:
|
case updDetails := <-ntfn3.Event.Updates:
|
||||||
expected := tx3NumConfs - i
|
expected := chainntnfs.TxUpdateInfo{
|
||||||
if numConfsLeft != expected {
|
NumConfsLeft: tx3NumConfs - i,
|
||||||
t.Fatalf("Received incorrect confirmation update: tx3 "+
|
BlockHeight: 12,
|
||||||
"expected %d confirmations left, got %d",
|
|
||||||
expected, numConfsLeft)
|
|
||||||
}
|
}
|
||||||
|
require.Equal(t, expected, updDetails)
|
||||||
default:
|
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
|
// TestTxNotifierSpendReorg ensures that clients are notified of a reorg when
|
||||||
// the spending transaction of an outpoint for which they registered a spend
|
// the spending transaction of an outpoint for which they registered a spend
|
||||||
// notification for has been reorged out of the chain.
|
// 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
|
// customBlob is an optional TLV encoded blob of data representing
|
||||||
// custom channel funding information.
|
// custom channel funding information.
|
||||||
customBlob tlv.OptionalRecordT[tlv.TlvType7, tlv.Blob]
|
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.
|
// 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.initialLocalBalance.Record(),
|
||||||
c.initialRemoteBalance.Record(),
|
c.initialRemoteBalance.Record(),
|
||||||
c.realScid.Record(),
|
c.realScid.Record(),
|
||||||
|
c.confirmationHeight.Record(),
|
||||||
}
|
}
|
||||||
c.memo.WhenSome(func(memo tlv.RecordT[tlv.TlvType5, []byte]) {
|
c.memo.WhenSome(func(memo tlv.RecordT[tlv.TlvType5, []byte]) {
|
||||||
tlvRecords = append(tlvRecords, memo.Record())
|
tlvRecords = append(tlvRecords, memo.Record())
|
||||||
@@ -283,6 +288,8 @@ func (c *openChannelTlvData) encode(w io.Writer) error {
|
|||||||
tlvRecords = append(tlvRecords, blob.Record())
|
tlvRecords = append(tlvRecords, blob.Record())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
tlv.SortRecords(tlvRecords)
|
||||||
|
|
||||||
// Create the tlv stream.
|
// Create the tlv stream.
|
||||||
tlvStream, err := tlv.NewStream(tlvRecords...)
|
tlvStream, err := tlv.NewStream(tlvRecords...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -307,6 +314,7 @@ func (c *openChannelTlvData) decode(r io.Reader) error {
|
|||||||
memo.Record(),
|
memo.Record(),
|
||||||
tapscriptRoot.Record(),
|
tapscriptRoot.Record(),
|
||||||
blob.Record(),
|
blob.Record(),
|
||||||
|
c.confirmationHeight.Record(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -906,6 +914,10 @@ type OpenChannel struct {
|
|||||||
// been confirmed before a certain height.
|
// been confirmed before a certain height.
|
||||||
FundingBroadcastHeight uint32
|
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
|
// NumConfsRequired is the number of confirmations a channel's funding
|
||||||
// transaction must have received in order to be considered available
|
// transaction must have received in order to be considered available
|
||||||
// for normal transactional use.
|
// for normal transactional use.
|
||||||
@@ -1207,6 +1219,7 @@ func (c *OpenChannel) amendTlvData(auxData openChannelTlvData) {
|
|||||||
auxData.initialRemoteBalance.Val,
|
auxData.initialRemoteBalance.Val,
|
||||||
)
|
)
|
||||||
c.confirmedScid = auxData.realScid.Val
|
c.confirmedScid = auxData.realScid.Val
|
||||||
|
c.ConfirmationHeight = auxData.confirmationHeight.Val
|
||||||
|
|
||||||
auxData.memo.WhenSomeV(func(memo []byte) {
|
auxData.memo.WhenSomeV(func(memo []byte) {
|
||||||
c.Memo = memo
|
c.Memo = memo
|
||||||
@@ -1234,6 +1247,9 @@ func (c *OpenChannel) extractTlvData() openChannelTlvData {
|
|||||||
realScid: tlv.NewRecordT[tlv.TlvType4](
|
realScid: tlv.NewRecordT[tlv.TlvType4](
|
||||||
c.confirmedScid,
|
c.confirmedScid,
|
||||||
),
|
),
|
||||||
|
confirmationHeight: tlv.NewPrimitiveRecord[tlv.TlvType8](
|
||||||
|
c.ConfirmationHeight,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(c.Memo) != 0 {
|
if len(c.Memo) != 0 {
|
||||||
@@ -1501,6 +1517,37 @@ func (c *OpenChannel) fullSync(tx kvdb.RwTx) error {
|
|||||||
return putOpenChannel(chanBucket, c)
|
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
|
// MarkAsOpen marks a channel as fully open given a locator that uniquely
|
||||||
// describes its location within the chain.
|
// describes its location within the chain.
|
||||||
func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error {
|
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")
|
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) {
|
func TestFetchPendingChannels(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -1007,7 +1074,7 @@ func TestFetchPendingChannels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
chanOpenLoc := lnwire.ShortChannelID{
|
chanOpenLoc := lnwire.ShortChannelID{
|
||||||
BlockHeight: 5,
|
BlockHeight: broadcastHeight + 1,
|
||||||
TxIndex: 10,
|
TxIndex: 10,
|
||||||
TxPosition: 15,
|
TxPosition: 15,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ reflect their state on the channel commitment without having to send or receive
|
|||||||
a certain amount of msats.
|
a certain amount of msats.
|
||||||
|
|
||||||
## Functional Enhancements
|
## 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
|
* RPCs `walletrpc.EstimateFee` and `walletrpc.FundPsbt` now
|
||||||
[allow](https://github.com/lightningnetwork/lnd/pull/10087)
|
[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
|
// process once the funding transaction has been broadcast. The primary
|
||||||
// function of waitForFundingConfirmation is to wait for blockchain
|
// function of waitForFundingConfirmation is to wait for blockchain
|
||||||
// confirmation, and then to notify the other systems that must be notified
|
// 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,
|
// The wait can be canceled by closing the cancelChan. In case of success,
|
||||||
// a *lnwire.ShortChannelID will be passed to confChan.
|
// 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",
|
log.Infof("Waiting for funding tx (%v) to reach %v confirmations",
|
||||||
txid, numConfs)
|
txid, numConfs)
|
||||||
|
|
||||||
var confDetails *chainntnfs.TxConfirmation
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
// Wait until the specified number of confirmations has been reached,
|
// Wait until the specified number of confirmations has been reached,
|
||||||
// we get a cancel signal, or the wallet signals a shutdown.
|
// we get a cancel signal, or the wallet signals a shutdown.
|
||||||
select {
|
for {
|
||||||
case confDetails, ok = <-confNtfn.Confirmed:
|
select {
|
||||||
// fallthrough
|
case updDetails, ok := <-confNtfn.Updates:
|
||||||
|
if !ok {
|
||||||
|
log.Warnf("ChainNotifier shutting down, "+
|
||||||
|
"cannot process updates for "+
|
||||||
|
"ChannelPoint(%v)",
|
||||||
|
completeChan.FundingOutpoint)
|
||||||
|
|
||||||
case <-cancelChan:
|
return
|
||||||
log.Warnf("canceled waiting for funding confirmation, "+
|
}
|
||||||
"stopping funding flow for ChannelPoint(%v)",
|
|
||||||
completeChan.FundingOutpoint)
|
|
||||||
return
|
|
||||||
|
|
||||||
case <-f.quit:
|
log.Debugf("funding tx %s received confirmation in "+
|
||||||
log.Warnf("fundingManager shutting down, stopping funding "+
|
"block %d, %d confirmations left", txid,
|
||||||
"flow for ChannelPoint(%v)",
|
updDetails.BlockHeight, updDetails.NumConfsLeft)
|
||||||
completeChan.FundingOutpoint)
|
|
||||||
return
|
// 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 {
|
// handleConfirmation is a helper function that constructs a ShortChannelID
|
||||||
log.Warnf("ChainNotifier shutting down, cannot complete "+
|
// based on the confirmation details and sends this information, along with the
|
||||||
"funding flow for ChannelPoint(%v)",
|
// funding transaction, to the provided confirmation channel.
|
||||||
completeChan.FundingOutpoint)
|
func (f *Manager) handleConfirmation(confDetails *chainntnfs.TxConfirmation,
|
||||||
return
|
completeChan *channeldb.OpenChannel,
|
||||||
}
|
confChan chan<- *confirmedChannel) error {
|
||||||
|
|
||||||
fundingPoint := completeChan.FundingOutpoint
|
fundingPoint := completeChan.FundingOutpoint
|
||||||
log.Infof("ChannelPoint(%v) is now active: ChannelID(%v)",
|
log.Infof("ChannelPoint(%v) is now active: ChannelID(%v)",
|
||||||
@@ -3157,8 +3259,10 @@ func (f *Manager) waitForFundingConfirmation(
|
|||||||
fundingTx: confDetails.Tx,
|
fundingTx: confDetails.Tx,
|
||||||
}:
|
}:
|
||||||
case <-f.quit:
|
case <-f.quit:
|
||||||
return
|
return fmt.Errorf("manager shutting down")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitForTimeout will close the timeout channel if MaxWaitNumBlocksFundingConf
|
// waitForTimeout will close the timeout channel if MaxWaitNumBlocksFundingConf
|
||||||
|
|||||||
+197
-81
@@ -178,9 +178,11 @@ func (m *mockAliasMgr) DeleteSixConfs(lnwire.ShortChannelID) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type mockNotifier struct {
|
type mockNotifier struct {
|
||||||
oneConfChannel chan *chainntnfs.TxConfirmation
|
oneConfChannel chan *chainntnfs.TxConfirmation
|
||||||
sixConfChannel chan *chainntnfs.TxConfirmation
|
sixConfChannel chan *chainntnfs.TxConfirmation
|
||||||
epochChan chan *chainntnfs.BlockEpoch
|
epochChan chan *chainntnfs.BlockEpoch
|
||||||
|
oneUpdateChannel chan chainntnfs.TxUpdateInfo
|
||||||
|
reOrgChan chan int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
||||||
@@ -190,11 +192,14 @@ func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
|
|||||||
|
|
||||||
if numConfs == 6 {
|
if numConfs == 6 {
|
||||||
return &chainntnfs.ConfirmationEvent{
|
return &chainntnfs.ConfirmationEvent{
|
||||||
Confirmed: m.sixConfChannel,
|
Confirmed: m.sixConfChannel,
|
||||||
|
NegativeConf: m.reOrgChan,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return &chainntnfs.ConfirmationEvent{
|
return &chainntnfs.ConfirmationEvent{
|
||||||
Confirmed: m.oneConfChannel,
|
Confirmed: m.oneConfChannel,
|
||||||
|
Updates: m.oneUpdateChannel,
|
||||||
|
NegativeConf: m.reOrgChan,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,9 +405,11 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
estimator := chainfee.NewStaticEstimator(62500, 0)
|
estimator := chainfee.NewStaticEstimator(62500, 0)
|
||||||
|
|
||||||
chainNotifier := &mockNotifier{
|
chainNotifier := &mockNotifier{
|
||||||
oneConfChannel: make(chan *chainntnfs.TxConfirmation, 1),
|
oneConfChannel: make(chan *chainntnfs.TxConfirmation, 1),
|
||||||
sixConfChannel: make(chan *chainntnfs.TxConfirmation, 1),
|
sixConfChannel: make(chan *chainntnfs.TxConfirmation, 1),
|
||||||
epochChan: make(chan *chainntnfs.BlockEpoch, 2),
|
epochChan: make(chan *chainntnfs.BlockEpoch, 2),
|
||||||
|
oneUpdateChannel: make(chan chainntnfs.TxUpdateInfo, 1),
|
||||||
|
reOrgChan: make(chan int32, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
aliasMgr := &mockAliasMgr{}
|
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,
|
func assertDatabaseState(t *testing.T, node *testNode,
|
||||||
fundingOutPoint *wire.OutPoint, expectedState channelOpeningState) {
|
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) {
|
func testNormalWorkflow(t *testing.T, chanType *lnwire.ChannelType) {
|
||||||
alice, bob := setupFundingManagers(t)
|
alice, bob := setupFundingManagers(t)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
@@ -1485,18 +1615,16 @@ func testNormalWorkflow(t *testing.T, chanType *lnwire.ChannelType) {
|
|||||||
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
|
||||||
chanType,
|
chanType,
|
||||||
)
|
)
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
|
||||||
|
|
||||||
// Check that neither Alice nor Bob sent an error message.
|
// Check that neither Alice nor Bob sent an error message.
|
||||||
assertErrorNotSent(t, alice.msgChan)
|
assertErrorNotSent(t, alice.msgChan)
|
||||||
assertErrorNotSent(t, bob.msgChan)
|
assertErrorNotSent(t, bob.msgChan)
|
||||||
|
|
||||||
// Notify that transaction was mined.
|
// Notify that the transaction was mined, and check that the
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
// confirmation height is set to 1 for both Alice and Bob.
|
||||||
Tx: fundingTx,
|
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
|
||||||
}
|
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
|
||||||
Tx: fundingTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// 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,
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
|
||||||
|
|
||||||
// After the funding transaction gets mined, both nodes will send the
|
// After the funding transaction gets mined, both nodes will send the
|
||||||
// channelReady message to the other peer. If the funding node fails
|
// 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
|
alice.fundingMgr.cfg.NotifyWhenOnline = notifyWhenOnline
|
||||||
|
|
||||||
// Notify that transaction was mined
|
// Notify that the transaction was mined, and check that the
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
// confirmation height is set to 1 for both Alice and Bob.
|
||||||
Tx: fundingTx,
|
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
|
||||||
}
|
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
|
||||||
Tx: fundingTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// 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,
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
|
||||||
|
|
||||||
// After the funding transaction gets mined, both nodes will send the
|
// After the funding transaction gets mined, both nodes will send the
|
||||||
// channelReady message to the other peer. If the funding node fails
|
// channelReady message to the other peer. If the funding node fails
|
||||||
@@ -1963,13 +2090,10 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
|
|||||||
conChan <- connected
|
conChan <- connected
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify that transaction was mined
|
// Notify that the transaction was mined, and check that the
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
// confirmation height is set to 1 for both Alice and Bob.
|
||||||
Tx: fundingTx,
|
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
|
||||||
}
|
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
|
||||||
Tx: fundingTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@@ -2458,14 +2582,12 @@ func TestFundingManagerReceiveChannelReadyTwice(t *testing.T) {
|
|||||||
fundingOutPoint, fundingTx := openChannel(
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
t, alice, bob, localAmt, pushAmt, 1, updateChan, true, nil,
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true, nil,
|
||||||
)
|
)
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
|
||||||
|
|
||||||
// Notify that transaction was mined
|
// Notify that the transaction was mined, and check that the
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
// confirmation height is set to 1 for both Alice and Bob.
|
||||||
Tx: fundingTx,
|
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
|
||||||
}
|
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
|
||||||
Tx: fundingTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@@ -2571,14 +2693,12 @@ func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
|
|||||||
fundingOutPoint, fundingTx := openChannel(
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
t, alice, bob, localAmt, pushAmt, 1, updateChan, true, nil,
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true, nil,
|
||||||
)
|
)
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
|
||||||
|
|
||||||
// Notify that transaction was mined
|
// Notify that the transaction was mined, and check that the
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
// confirmation height is set to 1 for both Alice and Bob.
|
||||||
Tx: fundingTx,
|
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
|
||||||
}
|
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
|
||||||
Tx: fundingTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@@ -2670,14 +2790,12 @@ func TestFundingManagerRestartAfterReceivingChannelReady(t *testing.T) {
|
|||||||
fundingOutPoint, fundingTx := openChannel(
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
t, alice, bob, localAmt, pushAmt, 1, updateChan, true, nil,
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, true, nil,
|
||||||
)
|
)
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
|
||||||
|
|
||||||
// Notify that transaction was mined
|
// Notify that the transaction was mined, and check that the
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
// confirmation height is set to 1 for both Alice and Bob.
|
||||||
Tx: fundingTx,
|
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
|
||||||
}
|
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
|
||||||
Tx: fundingTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@@ -2765,14 +2883,12 @@ func TestFundingManagerPrivateChannel(t *testing.T) {
|
|||||||
fundingOutPoint, fundingTx := openChannel(
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
t, alice, bob, localAmt, pushAmt, 1, updateChan, false, nil,
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, false, nil,
|
||||||
)
|
)
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
|
||||||
|
|
||||||
// Notify that transaction was mined
|
// Notify that the transaction was mined, and check that the
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
// confirmation height is set to 1 for both Alice and Bob.
|
||||||
Tx: fundingTx,
|
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
|
||||||
}
|
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
|
||||||
Tx: fundingTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// managers now have the state of this channel 'markedOpen' in their
|
||||||
@@ -2890,14 +3006,12 @@ func TestFundingManagerPrivateRestart(t *testing.T) {
|
|||||||
fundingOutPoint, fundingTx := openChannel(
|
fundingOutPoint, fundingTx := openChannel(
|
||||||
t, alice, bob, localAmt, pushAmt, 1, updateChan, false, nil,
|
t, alice, bob, localAmt, pushAmt, 1, updateChan, false, nil,
|
||||||
)
|
)
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(*fundingOutPoint)
|
||||||
|
|
||||||
// Notify that transaction was mined
|
// Notify that the transaction was mined, and check that the
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
// confirmation height is set to 1 for both Alice and Bob.
|
||||||
Tx: fundingTx,
|
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
|
||||||
}
|
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
|
||||||
Tx: fundingTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The funding transaction was mined, so assert that both funding
|
// The funding transaction was mined, so assert that both funding
|
||||||
// managers now have the state of this channel 'markedOpen' in their
|
// 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")
|
t.Fatalf("alice did not publish funding tx")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify that transaction was mined.
|
// Notify that the transaction was mined, and check that the
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
// confirmation height is set to 1 for both Alice and Bob.
|
||||||
Tx: fundingTx,
|
sendAndCheckFirstConfirmation(t, alice, fundingSigned.ChanID, fundingTx)
|
||||||
}
|
sendAndCheckFirstConfirmation(t, bob, fundingSigned.ChanID, fundingTx)
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
|
||||||
Tx: fundingTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
// After the funding transaction is mined, Alice will send
|
// After the funding transaction is mined, Alice will send
|
||||||
// channelReady to Bob.
|
// channelReady to Bob.
|
||||||
@@ -4550,6 +4661,7 @@ func testZeroConf(t *testing.T, chanType *lnwire.ChannelType) {
|
|||||||
Hash: fundingTx.TxHash(),
|
Hash: fundingTx.TxHash(),
|
||||||
Index: 0,
|
Index: 0,
|
||||||
}
|
}
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(*fundingOp)
|
||||||
|
|
||||||
// Assert that Bob's channel_ready message has an AliasScid.
|
// Assert that Bob's channel_ready message has an AliasScid.
|
||||||
bobChannelReady, ok := assertFundingMsgSent(
|
bobChannelReady, ok := assertFundingMsgSent(
|
||||||
@@ -4598,12 +4710,19 @@ func testZeroConf(t *testing.T, chanType *lnwire.ChannelType) {
|
|||||||
|
|
||||||
// We'll now confirm the funding transaction.
|
// We'll now confirm the funding transaction.
|
||||||
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
Tx: fundingTx,
|
Tx: fundingTx,
|
||||||
|
BlockHeight: 1,
|
||||||
}
|
}
|
||||||
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
|
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.
|
// For taproot channels, we don't expect them to be announced atm.
|
||||||
if !isTaprootChanType(chanType) {
|
if !isTaprootChanType(chanType) {
|
||||||
assertChannelAnnouncements(
|
assertChannelAnnouncements(
|
||||||
@@ -4833,6 +4952,7 @@ func TestFundingManagerCoinbase(t *testing.T) {
|
|||||||
Hash: fundingTx.TxHash(),
|
Hash: fundingTx.TxHash(),
|
||||||
Index: 0,
|
Index: 0,
|
||||||
}
|
}
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(*fundingOp)
|
||||||
|
|
||||||
chanFunder := &mockChanFunder{
|
chanFunder := &mockChanFunder{
|
||||||
fundingAmt: chanSize,
|
fundingAmt: chanSize,
|
||||||
@@ -4909,14 +5029,10 @@ func TestFundingManagerCoinbase(t *testing.T) {
|
|||||||
_, ok = pendingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
_, ok = pendingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
// Confirm the funding transaction.
|
// Notify that the transaction was mined, and check that the
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
// confirmation height is set to 1 for both Alice and Bob.
|
||||||
Tx: fundingTx,
|
sendAndCheckFirstConfirmation(t, alice, chanID, fundingTx)
|
||||||
}
|
sendAndCheckFirstConfirmation(t, bob, chanID, fundingTx)
|
||||||
|
|
||||||
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
|
||||||
Tx: fundingTx,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the notification about the pending channel was sent out.
|
// Make sure the notification about the pending channel was sent out.
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -62,6 +62,14 @@ var allTestCases = []*lntest.TestCase{
|
|||||||
Name: "funding expiry blocks on pending",
|
Name: "funding expiry blocks on pending",
|
||||||
TestFunc: testFundingExpiryBlocksOnPending,
|
TestFunc: testFundingExpiryBlocksOnPending,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "pending channel confirmation until active",
|
||||||
|
TestFunc: testPendingChannelConfirmationUntilActive,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "pending channel reorg test",
|
||||||
|
TestFunc: testPendingChannelAfterReorg,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "list channels",
|
Name: "list channels",
|
||||||
TestFunc: testListChannels,
|
TestFunc: testListChannels,
|
||||||
|
|||||||
@@ -877,6 +877,236 @@ func testFundingExpiryBlocksOnPending(ht *lntest.HarnessTest) {
|
|||||||
ht.MineBlocksAndAssertNumTxes(1, 1)
|
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
|
// testSimpleTaprootChannelActivation ensures that a simple taproot channel is
|
||||||
// active if the initiator disconnects and reconnects in between channel opening
|
// active if the initiator disconnects and reconnects in between channel opening
|
||||||
// and channel confirmation.
|
// 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
|
// very likely canceled the funding and the channel will never become
|
||||||
// fully operational.
|
// fully operational.
|
||||||
int32 funding_expiry_blocks = 3;
|
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 {
|
message WaitingCloseChannel {
|
||||||
|
|||||||
@@ -3337,6 +3337,16 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32",
|
"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."
|
"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
|
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
|
// fetchPendingOpenChannels queries the database for a list of channels that
|
||||||
// have pending open state. The returned result is used in the response of the
|
// have pending open state. The returned result is used in the response of the
|
||||||
// PendingChannels RPC.
|
// PendingChannels RPC.
|
||||||
@@ -3948,6 +3979,15 @@ func (r *rpcServer) fetchPendingOpenChannels() (pendingOpenChannels, error) {
|
|||||||
pendingChan.BroadcastHeight()
|
pendingChan.BroadcastHeight()
|
||||||
fundingExpiryBlocks := int32(maxFundingHeight) - currentHeight
|
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)
|
customChanBytes, err := encodeCustomChanData(pendingChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to encode open chan "+
|
return nil, fmt.Errorf("unable to encode open chan "+
|
||||||
@@ -3969,11 +4009,14 @@ func (r *rpcServer) fetchPendingOpenChannels() (pendingOpenChannels, error) {
|
|||||||
Memo: string(pendingChan.Memo),
|
Memo: string(pendingChan.Memo),
|
||||||
CustomChannelData: customChanBytes,
|
CustomChannelData: customChanBytes,
|
||||||
},
|
},
|
||||||
CommitWeight: commitWeight,
|
CommitWeight: commitWeight,
|
||||||
CommitFee: int64(localCommitment.CommitFee),
|
CommitFee: int64(localCommitment.CommitFee),
|
||||||
FeePerKw: int64(localCommitment.FeePerKw),
|
FeePerKw: int64(localCommitment.
|
||||||
FundingExpiryBlocks: fundingExpiryBlocks,
|
FeePerKw),
|
||||||
// TODO(roasbeef): need to track confirmation height
|
FundingExpiryBlocks: fundingExpiryBlocks,
|
||||||
|
ConfirmationsUntilActive: remainingConfs,
|
||||||
|
ConfirmationHeight: pendingChan.
|
||||||
|
ConfirmationHeight,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user