chainntnfs: validate conf/spend ntfn registration parameters

A height hint not being set would cause lnd to scan for the
confirmation/spend of a txid/outpoint/address from genesis.

The number of confirmations not being set within a confirmation request
would cause the internal TxNotifier to deadlock when dispatching
updates.
This commit is contained in:
Wilmer Paulino 2019-08-16 19:57:26 -07:00
parent 7821eb6ffb
commit 17b6205f3a
No known key found for this signature in database
GPG Key ID: 6DF57B9F9514972F
2 changed files with 116 additions and 29 deletions

@ -46,10 +46,22 @@ var (
// with the TxNotifier but it been shut down. // with the TxNotifier but it been shut down.
ErrTxNotifierExiting = errors.New("TxNotifier is exiting") ErrTxNotifierExiting = errors.New("TxNotifier is exiting")
// ErrTxMaxConfs signals that the user requested a number of // ErrNoScript is an error returned when a confirmation/spend
// confirmations beyond the reorg safety limit. // registration is attempted without providing an accompanying output
ErrTxMaxConfs = fmt.Errorf("too many confirmations requested, max is %d", // script.
MaxNumConfs) ErrNoScript = errors.New("an output script must be provided")
// ErrNoHeightHint is an error returned when a confirmation/spend
// registration is attempted without providing an accompanying height
// hint.
ErrNoHeightHint = errors.New("a height hint greater than 0 must be " +
"provided")
// ErrNumConfsOutOfRange is an error returned when a confirmation/spend
// registration is attempted and the number of confirmations provided is
// out of range.
ErrNumConfsOutOfRange = fmt.Errorf("number of confirmations must be "+
"between %d and %d", 1, MaxNumConfs)
) )
// rescanState indicates the progression of a registration before the notifier // rescanState indicates the progression of a registration before the notifier
@ -531,15 +543,27 @@ func NewTxNotifier(startHeight uint32, reorgSafetyLimit uint32,
func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash, func (n *TxNotifier) newConfNtfn(txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint uint32) (*ConfNtfn, error) { pkScript []byte, numConfs, heightHint uint32) (*ConfNtfn, error) {
confRequest, err := NewConfRequest(txid, pkScript) // An accompanying output script must always be provided.
if err != nil { if len(pkScript) == 0 {
return nil, err return nil, ErrNoScript
} }
// Enforce that we will not dispatch confirmations beyond the reorg // Enforce that we will not dispatch confirmations beyond the reorg
// safety limit. // safety limit.
if numConfs > n.reorgSafetyLimit { if numConfs == 0 || numConfs > n.reorgSafetyLimit {
return nil, ErrTxMaxConfs return nil, ErrNumConfsOutOfRange
}
// A height hint must be provided to prevent scanning from the genesis
// block.
if heightHint == 0 {
return nil, ErrNoHeightHint
}
// Ensure the output script is of a supported type.
confRequest, err := NewConfRequest(txid, pkScript)
if err != nil {
return nil, err
} }
confID := atomic.AddUint64(&n.confClientCounter, 1) confID := atomic.AddUint64(&n.confClientCounter, 1)
@ -910,6 +934,18 @@ func (n *TxNotifier) dispatchConfDetails(
func (n *TxNotifier) newSpendNtfn(outpoint *wire.OutPoint, func (n *TxNotifier) newSpendNtfn(outpoint *wire.OutPoint,
pkScript []byte, heightHint uint32) (*SpendNtfn, error) { pkScript []byte, heightHint uint32) (*SpendNtfn, error) {
// An accompanying output script must always be provided.
if len(pkScript) == 0 {
return nil, ErrNoScript
}
// A height hint must be provided to prevent scanning from the genesis
// block.
if heightHint == 0 {
return nil, ErrNoHeightHint
}
// Ensure the output script is of a supported type.
spendRequest, err := NewSpendRequest(outpoint, pkScript) spendRequest, err := NewSpendRequest(outpoint, pkScript)
if err != nil { if err != nil {
return nil, err return nil, err

@ -124,30 +124,81 @@ func newMockHintCache() *mockHintCache {
} }
} }
// TestTxNotifierMaxConfs ensures that we are not able to register for more // TestTxNotifierRegistrationValidation ensures that we are not able to register
// confirmations on a transaction than the maximum supported. // requests with invalid parameters.
func TestTxNotifierMaxConfs(t *testing.T) { func TestTxNotifierRegistrationValidation(t *testing.T) {
t.Parallel() t.Parallel()
hintCache := newMockHintCache() testCases := []struct {
n := chainntnfs.NewTxNotifier( name string
10, chainntnfs.ReorgSafetyLimit, hintCache, hintCache, pkScript []byte
) numConfs uint32
heightHint uint32
// Registering one confirmation above the maximum should fail with checkSpend bool
// ErrTxMaxConfs. err error
_, err := n.RegisterConf( }{
&chainntnfs.ZeroHash, testRawScript, chainntnfs.MaxNumConfs+1, 1, {
) name: "empty output script",
if err != chainntnfs.ErrTxMaxConfs { pkScript: nil,
t.Fatalf("expected chainntnfs.ErrTxMaxConfs, got %v", err) numConfs: 1,
heightHint: 1,
checkSpend: true,
err: chainntnfs.ErrNoScript,
},
{
name: "zero num confs",
pkScript: testRawScript,
numConfs: 0,
heightHint: 1,
err: chainntnfs.ErrNumConfsOutOfRange,
},
{
name: "exceed max num confs",
pkScript: testRawScript,
numConfs: chainntnfs.MaxNumConfs + 1,
heightHint: 1,
err: chainntnfs.ErrNumConfsOutOfRange,
},
{
name: "empty height hint",
pkScript: testRawScript,
numConfs: 1,
heightHint: 0,
checkSpend: true,
err: chainntnfs.ErrNoHeightHint,
},
} }
_, err = n.RegisterConf( for _, testCase := range testCases {
&chainntnfs.ZeroHash, testRawScript, chainntnfs.MaxNumConfs, 1, testCase := testCase
) t.Run(testCase.name, func(t *testing.T) {
if err != nil { hintCache := newMockHintCache()
t.Fatalf("unable to register conf ntfn: %v", err) n := chainntnfs.NewTxNotifier(
10, chainntnfs.ReorgSafetyLimit, hintCache, hintCache,
)
_, err := n.RegisterConf(
&chainntnfs.ZeroHash, testCase.pkScript,
testCase.numConfs, testCase.heightHint,
)
if err != testCase.err {
t.Fatalf("conf registration expected error "+
"\"%v\", got \"%v\"", testCase.err, err)
}
if !testCase.checkSpend {
return
}
_, err = n.RegisterSpend(
&chainntnfs.ZeroOutPoint, testCase.pkScript,
testCase.heightHint,
)
if err != testCase.err {
t.Fatalf("spend registration expected error "+
"\"%v\", got \"%v\"", testCase.err, err)
}
})
} }
} }