-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Expose confirmation count for pending 'channel open' transactions #9677
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
6361adc
673f71f
33527d6
78b6d20
8a13679
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -978,6 +978,81 @@ func TestChannelStateTransition(t *testing.T) { | |
require.Empty(t, fwdPkgs, "no forwarding packages should exist") | ||
} | ||
|
||
// TestOpeningChannelTxConfirmation verifies that calling MarkConfirmedScid | ||
// correctly updates the confirmed state. It also ensures that calling Refresh | ||
// on a different OpenChannel updates its in-memory state to reflect the prior | ||
// MarkConfirmedScid call. | ||
func TestOpeningChannelTxConfirmation(t *testing.T) { | ||
t.Parallel() | ||
|
||
fullDB, err := MakeTestDB(t) | ||
require.NoError(t, err, "unable to make test database") | ||
|
||
cdb := fullDB.ChannelStateDB() | ||
|
||
// Create a pending channel that was broadcast at height 99. | ||
const broadcastHeight = uint32(99) | ||
channelState := createTestChannel(t, cdb, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Formatting |
||
pendingHeightOption(broadcastHeight)) | ||
|
||
// Fetch pending channels from the database. | ||
pendingChannels, err := cdb.FetchPendingChannels() | ||
require.NoError(t, err, "unable to list pending channels") | ||
require.Len(t, pendingChannels, 1, "expected one pending channel") | ||
|
||
// Verify the broadcast height of the pending channel. | ||
require.Equal(t, broadcastHeight, pendingChannels[0]. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Formatting. |
||
FundingBroadcastHeight, "broadcast height mismatch") | ||
|
||
confirmedScid := lnwire.ShortChannelID{ | ||
BlockHeight: broadcastHeight + 1, | ||
TxIndex: 10, | ||
TxPosition: 15, | ||
Comment on lines
+1009
to
+1010
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we leave them empty, seems like they are ot used anyways ? |
||
} | ||
|
||
// Mark the channel as confirmed. | ||
err = pendingChannels[0].MarkConfirmedScid(confirmedScid) | ||
require.NoError(t, err, "unable to mark channel as confirmed") | ||
|
||
// Ensure the channel remains pending after confirmation. | ||
require.True(t, pendingChannels[0].IsPending, "channel should remain "+ | ||
"pending after confirmation") | ||
|
||
// Verify the ShortChannelID is updated correctly. | ||
require.Equal(t, confirmedScid, pendingChannels[0].ShortChanID(), | ||
"channel ShortChannelID not updated correctly") | ||
|
||
// Re-fetch the pending channels to confirm persistence. | ||
pendingChannels, err = cdb.FetchPendingChannels() | ||
require.NoError(t, err, "unable to list pending channels") | ||
require.Len(t, pendingChannels, 1, "expected one pending channel") | ||
|
||
// Validate the ShortChannelID and broadcast height. | ||
require.Equal(t, confirmedScid, pendingChannels[0].ShortChanID(), | ||
"channel ShortChannelID mismatch after re-fetching") | ||
require.Equal(t, broadcastHeight, pendingChannels[0]. | ||
FundingBroadcastHeight, "broadcast height mismatch after "+ | ||
"re-fetching") | ||
|
||
// Ensure the original channel state's ShortChannelID is not updated | ||
// before refresh. | ||
require.NotEqual(t, channelState.ShortChanID(), pendingChannels[0]. | ||
ShortChanID(), "original channel state's ShortChannelID "+ | ||
"should not match before refresh") | ||
|
||
// Refresh the original channel state. | ||
err = channelState.Refresh() | ||
require.NoError(t, err, "unable to refresh channel state") | ||
|
||
// Verify that both channel states now have the same ShortChannelID. | ||
require.Equal(t, channelState.ShortChanID(), pendingChannels[0]. | ||
ShortChanID(), "channel ShortChannelID mismatch after refresh") | ||
|
||
// Confirm the channel remains pending after refresh. | ||
require.True(t, channelState.IsPending, "channel should remain "+ | ||
"pending after refresh") | ||
} | ||
|
||
func TestFetchPendingChannels(t *testing.T) { | ||
t.Parallel() | ||
|
||
|
@@ -1007,7 +1082,7 @@ func TestFetchPendingChannels(t *testing.T) { | |
} | ||
|
||
chanOpenLoc := lnwire.ShortChannelID{ | ||
BlockHeight: 5, | ||
BlockHeight: broadcastHeight + 1, | ||
TxIndex: 10, | ||
TxPosition: 15, | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3012,6 +3012,16 @@ func (f *Manager) waitForFundingWithTimeout( | |
confChan := make(chan *confirmedChannel) | ||
timeoutChan := make(chan error, 1) | ||
cancelChan := make(chan struct{}) | ||
errorChan := make(chan error, 1) | ||
|
||
// If the channel is not a zero-conf channel, we add the SCID to the | ||
// database once the channel opening transaction receives one | ||
// confirmation. This enables us to calculate the number of | ||
// confirmations before the pending channel becomes active. | ||
if !ch.IsZeroConf() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not necessary, for can use the function |
||
f.wg.Add(1) | ||
go f.handleOpenChanTxConfirmation(ch, cancelChan, errorChan) | ||
} | ||
|
||
f.wg.Add(1) | ||
go f.waitForFundingConfirmation(ch, cancelChan, confChan) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this function listen to the Make sure that you also make sure when the NumConf is equal to 1. Can we be sure that the Updates channel fire always prior to the Confirmation Channel ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But doesn’t There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, but I still propose using it, and instead adding a new tlv field to the OpenChannelStruct (in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then we can also leave There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I also like it more, because I think the ShortChan ID should only be set when the channel is active not when it is still unusable. |
||
|
@@ -3025,24 +3035,40 @@ func (f *Manager) waitForFundingWithTimeout( | |
} | ||
defer close(cancelChan) | ||
|
||
select { | ||
case err := <-timeoutChan: | ||
if err != nil { | ||
return nil, err | ||
} | ||
return nil, ErrConfirmationTimeout | ||
for { | ||
select { | ||
case err := <-errorChan: | ||
if err != nil { | ||
return nil, fmt.Errorf("waiting for funding"+ | ||
"confirmation failed: %v", err) | ||
} | ||
|
||
case <-f.quit: | ||
// The fundingManager is shutting down, and will resume wait on | ||
// startup. | ||
return nil, ErrFundingManagerShuttingDown | ||
// If the channel opening transaction receives one | ||
// confirmation successfully, set errorChan to nil to | ||
// disable this case in the select statement for | ||
// subsequent channel messages. | ||
errorChan = nil | ||
|
||
case confirmedChannel, ok := <-confChan: | ||
if !ok { | ||
return nil, fmt.Errorf("waiting for funding" + | ||
"confirmation failed") | ||
case err := <-timeoutChan: | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return nil, ErrConfirmationTimeout | ||
|
||
case <-f.quit: | ||
// The fundingManager is shutting down, and will resume | ||
// wait on startup. | ||
return nil, ErrFundingManagerShuttingDown | ||
|
||
case confirmedChannel, ok := <-confChan: | ||
if !ok { | ||
return nil, fmt.Errorf("waiting for funding" + | ||
"confirmation failed") | ||
} | ||
|
||
return confirmedChannel, nil | ||
} | ||
return confirmedChannel, nil | ||
} | ||
} | ||
|
||
|
@@ -3075,6 +3101,87 @@ func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) { | |
return input.WitnessScriptHash(multiSigScript) | ||
} | ||
|
||
// handleOpenChanTxConfirmation manages the confirmation process of a channel's | ||
// funding transaction. It registers for confirmation notifications, waits for | ||
// the funding transaction to be confirmed, updates the channel state, and | ||
// handles shutdown signals or cancellations. | ||
// | ||
// NOTE: This MUST be run as a goroutine. | ||
func (f *Manager) handleOpenChanTxConfirmation(openChannel *channeldb. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we don't need this. |
||
OpenChannel, cancelChan <-chan struct{}, errorChan chan<- error) { | ||
|
||
defer f.wg.Done() | ||
defer close(errorChan) | ||
|
||
// Generate the funding output script needed to detect the transaction | ||
// confirmation | ||
chanFundingScript, err := makeFundingScript(openChannel) | ||
if err != nil { | ||
errorChan <- fmt.Errorf("unable to create funding script for "+ | ||
"ChannelPoint(%v): %v", openChannel.FundingOutpoint, | ||
err) | ||
|
||
return | ||
} | ||
|
||
// Register for transaction confirmation notifications | ||
chanConfNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn( | ||
&openChannel.FundingOutpoint.Hash, chanFundingScript, 1, | ||
openChannel.BroadcastHeight(), | ||
) | ||
if err != nil { | ||
errorChan <- fmt.Errorf("unable to register for confirmation "+ | ||
"of ChannelPoint(%v): %v", openChannel.FundingOutpoint, | ||
err) | ||
|
||
return | ||
} | ||
|
||
var confDetails *chainntnfs.TxConfirmation | ||
var ok bool | ||
|
||
// Wait for either the funding confirmation, cancellation, or manager | ||
// shutdown | ||
select { | ||
case confDetails, ok = <-chanConfNtfn.Confirmed: | ||
// fallthrough | ||
|
||
case <-cancelChan: | ||
// canceled waiting for funding confirmation | ||
return | ||
|
||
case <-f.quit: | ||
// fundingManager is shutting down | ||
return | ||
} | ||
|
||
if !ok { | ||
errorChan <- fmt.Errorf("ChainNotifier shutting down, can't "+ | ||
"complete funding flow for ChannelPoint(%v)", | ||
openChannel.FundingOutpoint) | ||
|
||
return | ||
} | ||
|
||
fundingPoint := openChannel.FundingOutpoint | ||
log.Infof("ChannelPoint(%v) confirmed at block %d", | ||
fundingPoint, confDetails.BlockHeight) | ||
|
||
// Construct short channel ID from confirmation details | ||
shortChanID := lnwire.ShortChannelID{ | ||
BlockHeight: confDetails.BlockHeight, | ||
TxIndex: confDetails.TxIndex, | ||
TxPosition: uint16(fundingPoint.Index), | ||
} | ||
|
||
// Update the channel's state in the database to mark its confirmed SCID | ||
err = openChannel.MarkConfirmedScid(shortChanID) | ||
if err != nil { | ||
errorChan <- fmt.Errorf("failed to update confirmed state for "+ | ||
"ChannelPoint(%v): %v", fundingPoint, err) | ||
} | ||
} | ||
|
||
// waitForFundingConfirmation handles the final stages of the channel funding | ||
// process once the funding transaction has been broadcast. The primary | ||
// function of waitForFundingConfirmation is to wait for blockchain | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What we need to do is to split what we are currently doing in
func (c *OpenChannel) MarkAsOpen(openLoc lnwire.ShortChannelID) error {
We need to split this function into two:
MarkShortChannelID
MarkAsOpen
=> removing the logic where we persit the short channel id there otherwise we make it two times for the non-zeroconf-channel.