Merge pull request #3569 from guggero/chainntfs-addr-reuse
chainntnfs: fix nil pointer on address reuse
This commit is contained in:
commit
fd906475dd
@ -103,6 +103,9 @@ type confNtfnSet struct {
|
|||||||
// details serves as a cache of the confirmation details of a
|
// details serves as a cache of the confirmation details of a
|
||||||
// transaction that we'll use to determine if a transaction/output
|
// transaction that we'll use to determine if a transaction/output
|
||||||
// script has already confirmed at the time of registration.
|
// script has already confirmed at the time of registration.
|
||||||
|
// details is also used to make sure that in case of an address reuse
|
||||||
|
// (funds sent to a previously confirmed script) no additional
|
||||||
|
// notification is registered which would lead to an inconsistent state.
|
||||||
details *TxConfirmation
|
details *TxConfirmation
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1507,6 +1510,15 @@ func (n *TxNotifier) handleConfDetailsAtTip(confRequest ConfRequest,
|
|||||||
|
|
||||||
// TODO(wilmer): cancel pending historical rescans if any?
|
// TODO(wilmer): cancel pending historical rescans if any?
|
||||||
confSet := n.confNotifications[confRequest]
|
confSet := n.confNotifications[confRequest]
|
||||||
|
|
||||||
|
// If we already have details for this request, we don't want to add it
|
||||||
|
// again since we have already dispatched notifications for it.
|
||||||
|
if confSet.details != nil {
|
||||||
|
Log.Warnf("Ignoring address reuse for %s at height %d.",
|
||||||
|
confRequest, details.BlockHeight)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
confSet.rescanStatus = rescanComplete
|
confSet.rescanStatus = rescanComplete
|
||||||
confSet.details = details
|
confSet.details = details
|
||||||
|
|
||||||
|
@ -604,6 +604,166 @@ func TestTxNotifierFutureSpendDispatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTxNotifierFutureConfDispatchReuseSafe tests that the notifier does not
|
||||||
|
// misbehave even if two confirmation requests for the same script are issued
|
||||||
|
// at different block heights (which means funds are being sent to the same
|
||||||
|
// script multiple times).
|
||||||
|
func TestTxNotifierFutureConfDispatchReuseSafe(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
currentBlock := uint32(10)
|
||||||
|
hintCache := newMockHintCache()
|
||||||
|
n := chainntnfs.NewTxNotifier(
|
||||||
|
currentBlock, 2, hintCache, hintCache,
|
||||||
|
)
|
||||||
|
|
||||||
|
// We'll register a TX that sends to our test script and put it into a
|
||||||
|
// block. Additionally we register a notification request for just the
|
||||||
|
// script which should also be confirmed with that block.
|
||||||
|
tx1 := wire.MsgTx{Version: 1}
|
||||||
|
tx1.AddTxOut(&wire.TxOut{PkScript: testRawScript})
|
||||||
|
tx1Hash := tx1.TxHash()
|
||||||
|
ntfn1, err := n.RegisterConf(&tx1Hash, testRawScript, 1, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to register ntfn: %v", err)
|
||||||
|
}
|
||||||
|
scriptNtfn1, err := n.RegisterConf(nil, testRawScript, 1, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to register ntfn: %v", err)
|
||||||
|
}
|
||||||
|
block := btcutil.NewBlock(&wire.MsgBlock{
|
||||||
|
Transactions: []*wire.MsgTx{&tx1},
|
||||||
|
})
|
||||||
|
currentBlock++
|
||||||
|
err = n.ConnectTip(block.Hash(), currentBlock, block.Transactions())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to connect block: %v", err)
|
||||||
|
}
|
||||||
|
if err := n.NotifyHeight(currentBlock); err != nil {
|
||||||
|
t.Fatalf("unable to dispatch notifications: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect an update and confirmation of TX 1 at this point. We save the
|
||||||
|
// confirmation details because we expect to receive the same details
|
||||||
|
// for all further registrations.
|
||||||
|
var confDetails *chainntnfs.TxConfirmation
|
||||||
|
select {
|
||||||
|
case <-ntfn1.Event.Updates:
|
||||||
|
default:
|
||||||
|
t.Fatal("expected update of TX 1")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case confDetails = <-ntfn1.Event.Confirmed:
|
||||||
|
if confDetails.BlockHeight != currentBlock {
|
||||||
|
t.Fatalf("expected TX to be confirmed in latest block")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatal("expected confirmation of TX 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The notification for the script should also have received a
|
||||||
|
// confirmation.
|
||||||
|
select {
|
||||||
|
case <-scriptNtfn1.Event.Updates:
|
||||||
|
default:
|
||||||
|
t.Fatal("expected update of script ntfn")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case details := <-scriptNtfn1.Event.Confirmed:
|
||||||
|
assertConfDetails(t, details, confDetails)
|
||||||
|
default:
|
||||||
|
t.Fatal("expected update of script ntfn")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now register a second TX that spends to two outputs with the same
|
||||||
|
// script so we have a different TXID. And again register a confirmation
|
||||||
|
// for just the script.
|
||||||
|
tx2 := wire.MsgTx{Version: 1}
|
||||||
|
tx2.AddTxOut(&wire.TxOut{PkScript: testRawScript})
|
||||||
|
tx2.AddTxOut(&wire.TxOut{PkScript: testRawScript})
|
||||||
|
tx2Hash := tx2.TxHash()
|
||||||
|
ntfn2, err := n.RegisterConf(&tx2Hash, testRawScript, 1, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to register ntfn: %v", err)
|
||||||
|
}
|
||||||
|
scriptNtfn2, err := n.RegisterConf(nil, testRawScript, 1, 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to register ntfn: %v", err)
|
||||||
|
}
|
||||||
|
block2 := btcutil.NewBlock(&wire.MsgBlock{
|
||||||
|
Transactions: []*wire.MsgTx{&tx2},
|
||||||
|
})
|
||||||
|
currentBlock++
|
||||||
|
err = n.ConnectTip(block2.Hash(), currentBlock, block2.Transactions())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to connect block: %v", err)
|
||||||
|
}
|
||||||
|
if err := n.NotifyHeight(currentBlock); err != nil {
|
||||||
|
t.Fatalf("unable to dispatch notifications: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction 2 should get a confirmation here too. Since it was
|
||||||
|
// a different TXID we wouldn't get the cached details here but the TX
|
||||||
|
// should be confirmed right away still.
|
||||||
|
select {
|
||||||
|
case <-ntfn2.Event.Updates:
|
||||||
|
default:
|
||||||
|
t.Fatal("expected update of TX 2")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case details := <-ntfn2.Event.Confirmed:
|
||||||
|
if details.BlockHeight != currentBlock {
|
||||||
|
t.Fatalf("expected TX to be confirmed in latest block")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatal("expected update of TX 2")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The second notification for the script should also have received a
|
||||||
|
// confirmation. Since it's the same script, we expect to get the cached
|
||||||
|
// details from the first TX back immediately. Nothing should be
|
||||||
|
// registered at the notifier for the current block height for that
|
||||||
|
// script any more.
|
||||||
|
select {
|
||||||
|
case <-scriptNtfn2.Event.Updates:
|
||||||
|
default:
|
||||||
|
t.Fatal("expected update of script ntfn")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case details := <-scriptNtfn2.Event.Confirmed:
|
||||||
|
assertConfDetails(t, details, confDetails)
|
||||||
|
default:
|
||||||
|
t.Fatal("expected update of script ntfn")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, mine a few empty blocks and expect both TXs to be confirmed.
|
||||||
|
for currentBlock < 15 {
|
||||||
|
block := btcutil.NewBlock(&wire.MsgBlock{})
|
||||||
|
currentBlock++
|
||||||
|
err = n.ConnectTip(
|
||||||
|
block.Hash(), currentBlock, block.Transactions(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to connect block: %v", err)
|
||||||
|
}
|
||||||
|
if err := n.NotifyHeight(currentBlock); err != nil {
|
||||||
|
t.Fatalf("unable to dispatch notifications: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events for both confirmation requests should have been dispatched.
|
||||||
|
select {
|
||||||
|
case <-ntfn1.Event.Done:
|
||||||
|
default:
|
||||||
|
t.Fatal("expected notifications for TX 1 to be done")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ntfn2.Event.Done:
|
||||||
|
default:
|
||||||
|
t.Fatal("expected notifications for TX 2 to be done")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestTxNotifierHistoricalSpendDispatch tests that the TxNotifier dispatches
|
// TestTxNotifierHistoricalSpendDispatch tests that the TxNotifier dispatches
|
||||||
// registered notifications when an outpoint is spent before registration.
|
// registered notifications when an outpoint is spent before registration.
|
||||||
func TestTxNotifierHistoricalSpendDispatch(t *testing.T) {
|
func TestTxNotifierHistoricalSpendDispatch(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user