Skip to content
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

chainntnfs: remove block info from conf detail copy #7859

Merged
merged 1 commit into from
Aug 3, 2023

Conversation

guggero
Copy link
Collaborator

@guggero guggero commented Jul 28, 2023

Noticed this while updating to latest master in another project: If there are two notifications for the same output being registered and the first one does NOT request the block to be included, then the second one also will not receive the block, even if it explicitly requests it.
This is caused by the block being removed from the original confirmation set instead of a copy (as it is done in NotifyHeight and UpdateConfDetails).

Fixes #7501

Copy link
Member

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix! LGTM⛵️

@Roasbeef
Copy link
Member

Fixes #7501?

@guggero
Copy link
Collaborator Author

guggero commented Jul 28, 2023

Yes! Wasn't aware there was already an issue.

@Roasbeef
Copy link
Member

So I tried to write a test to confirm the fix, and after messing with a diff few iterations (conf before registration, conf after, switch order of registration, etc, etc), it still seems to pass:

diff --git a/chainntnfs/test/test_interface.go b/chainntnfs/test/test_interface.go
index 9def356c6..774bb1702 100644
--- a/chainntnfs/test/test_interface.go
+++ b/chainntnfs/test/test_interface.go
@@ -25,6 +25,7 @@ import (
 	"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
 	"github.com/lightningnetwork/lnd/chainntnfs/neutrinonotify"
 	"github.com/lightningnetwork/lnd/channeldb"
+	"github.com/lightningnetwork/lnd/lnutils"
 	"github.com/stretchr/testify/require"
 )
 
@@ -1709,6 +1710,55 @@ func testCatchUpOnMissedBlocksWithReorg(miner1 *rpctest.Harness,
 	}
 }
 
+// testIncludeBlockAsymmetry tests that if the same output is registered for a
+// notification by two callers, one is able to get a notification with the
+// block and the other one without it.
+func testIncludeBlockAsymmetry(miner *rpctest.Harness,
+	notifier chainntnfs.TestChainNotifier, scriptDispatch bool,
+	t *testing.T) {
+
+	// We'll start by creating a new test transaction, waiting long enough
+	// for it to get into the mempool.
+	txid, pkScript, err := chainntnfs.GetTestTxidAndScript(miner)
+	require.NoError(t, err, "unable to create test tx")
+	if err := chainntnfs.WaitForMempoolTx(miner, txid); err != nil {
+		t.Fatalf("tx not relayed to miner: %v", err)
+	}
+
+	_, currentHeight, err := miner.Client.GetBestBlock()
+	require.NoError(t, err, "unable to get current height")
+
+	// Next, we'll generate a single block, which should confirm the
+	// transaction created above.
+	_, err = miner.Client.Generate(1)
+	require.NoError(t, err, "unable to generate single block")
+
+	numConfs := uint32(1)
+
+	confIntentNoBlock, err := notifier.RegisterConfirmationsNtfn(
+		nil, pkScript, numConfs, uint32(currentHeight),
+	)
+	require.NoError(t, err)
+
+	confNtfnNoBlock, err := lnutils.RecvOrTimeout(
+		confIntentNoBlock.Confirmed, time.Second*5,
+	)
+	require.NoError(t, err)
+	require.True(t, (*confNtfnNoBlock).Block == nil, "block was included")
+
+	confIntentBlock, err := notifier.RegisterConfirmationsNtfn(
+		nil, pkScript, numConfs, uint32(currentHeight),
+		chainntnfs.WithIncludeBlock(),
+	)
+	require.NoError(t, err)
+
+	confNtfnBlock, err := lnutils.RecvOrTimeout(
+		confIntentBlock.Confirmed, time.Second*5,
+	)
+	require.NoError(t, err)
+	require.True(t, (*confNtfnBlock).Block != nil, "block not included")
+}
+
 type txNtfnTestCase struct {
 	name string
 	test func(node *rpctest.Harness, notifier chainntnfs.TestChainNotifier,
@@ -1728,7 +1778,7 @@ type blockCatchupTestCase struct {
 }
 
 var txNtfnTests = []txNtfnTestCase{
-	{
+	/*{
 		name: "single conf ntfn",
 		test: testSingleConfirmationNotification,
 	},
@@ -1771,22 +1821,26 @@ var txNtfnTests = []txNtfnTestCase{
 	{
 		name: "cancel spend ntfn",
 		test: testCancelSpendNtfn,
+	},*/
+	{
+		name: "test include block asymmetry",
+		test: testIncludeBlockAsymmetry,
 	},
 }
 
 var blockNtfnTests = []blockNtfnTestCase{
-	{
+	/*{
 		name: "block epoch",
 		test: testBlockEpochNotification,
 	},
 	{
 		name: "cancel epoch ntfn",
 		test: testCancelEpochNtfn,
-	},
+	},*/
 }
 
 var blockCatchupTests = []blockCatchupTestCase{
-	{
+	/*{
 		name: "catch up client on historical block epoch ntfns",
 		test: testCatchUpClientOnMissedBlocks,
 	},
@@ -1797,7 +1851,7 @@ var blockCatchupTests = []blockCatchupTestCase{
 	{
 		name: "test catch up on missed blocks w/ reorged best block",
 		test: testCatchUpOnMissedBlocksWithReorg,
-	},
+	},*/
 }
 
 // TestInterfaces tests all registered interfaces with a unified set of tests

In the wild, I would observe a panic in lndclient as it would try to deref the block as it assumed it was properly included when in fact it wasn't.

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix looks good, but having trouble writing a unit test to repro and confirm the fix, see comment below.

Noticed this while updating to latest master in another project:
If there are two notifications for the same output being registered and
the first one does _NOT_ request the block to be included, then the
second one also will not receive the block, even if it explicitly
requests it.
This is caused by the block being removed from the original confirmation
set instead of a copy (as it is done in NotifyHeight and
UpdateConfDetails).
@guggero
Copy link
Collaborator Author

guggero commented Aug 2, 2023

@Roasbeef thanks a lot for the patch! That gave me the required inspiration to be able to reproduce.
So the reproduction is a tiny bit more involved than what you attempted. We basically need to trigger the rescan result from a previous client. So I just ended up doing the same notification registrations twice and now it correctly triggers the fixed code path.

@guggero guggero requested a review from Roasbeef August 2, 2023 13:25
@Roasbeef
Copy link
Member

Roasbeef commented Aug 3, 2023

We basically need to trigger the rescan result from a previous client. So I just ended up doing the same notification registrations twice and now it correctly triggers the fixed code path.

Ahh sure, I knew I was missing something subtle. Will check out the latest diff.

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 💼

@Roasbeef Roasbeef merged commit 03b26bb into lightningnetwork:master Aug 3, 2023
22 of 24 checks passed
@guggero guggero deleted the txnotifier-details-copy branch August 4, 2023 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[bug]: notifier doesn't always return block over RPC when client requests it
3 participants