diff --git a/Gopkg.lock b/Gopkg.lock index 844640b4a..8f2127747 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -79,7 +79,7 @@ "txscript", "wire" ] - revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64" + revision = "9a2f9524024889e129a5422aca2cff73cb3eabf6" [[projects]] name = "github.com/btcsuite/btclog" @@ -97,7 +97,7 @@ "hdkeychain", "txsort" ] - revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" + revision = "ab6388e0c60ae4834a1f57511e20c17b5f78be4b" [[projects]] name = "github.com/btcsuite/btcwallet" @@ -118,7 +118,7 @@ "walletdb/bdb", "wtxmgr" ] - revision = "64b5b448f5e6853a2d870f388504a7807bc951f1" + revision = "a4d9da433fcfaeec50d3e9657e6d511d14cddadf" [[projects]] branch = "master" @@ -231,7 +231,7 @@ "filterdb", "headerfs" ] - revision = "03f4c660ea0d1586331f32561185d45eab1ba4c9" + revision = "d5054cea8fe43c324a473e8aa7e0f6dd93622125" [[projects]] name = "github.com/lightningnetwork/lightning-onion" @@ -404,6 +404,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "7ac5bd06ab8aa8f6a01f191092ac37d1e33200f366fa3b52fb4152369d76c5be" + inputs-digest = "dcfe757e222cc26b43d926797fb0cb9f87a18da54dca6615e701eb715c35b375" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 32d09da74..6b05d65e1 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -44,7 +44,7 @@ [[constraint]] name = "github.com/lightninglabs/neutrino" - revision = "03f4c660ea0d1586331f32561185d45eab1ba4c9" + revision = "d5054cea8fe43c324a473e8aa7e0f6dd93622125" [[constraint]] name = "github.com/lightningnetwork/lightning-onion" @@ -64,15 +64,15 @@ [[constraint]] name = "github.com/btcsuite/btcutil" - revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4" + revision = "ab6388e0c60ae4834a1f57511e20c17b5f78be4b" [[constraint]] name = "github.com/btcsuite/btcd" - revision = "86fed781132ac890ee03e906e4ecd5d6fa180c64" + revision = "9a2f9524024889e129a5422aca2cff73cb3eabf6" [[constraint]] name = "github.com/btcsuite/btcwallet" - revision = "64b5b448f5e6853a2d870f388504a7807bc951f1" + revision = "a4d9da433fcfaeec50d3e9657e6d511d14cddadf" [[constraint]] name = "github.com/tv42/zbase32" diff --git a/breacharbiter.go b/breacharbiter.go index 4d31ca369..0862acd82 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -196,8 +196,10 @@ func (b *breachArbiter) Start() error { // Register for a notification when the breach transaction is // confirmed on chain. breachTXID := retInfo.commitHash + breachScript := retInfo.breachedOutputs[0].signDesc.Output.PkScript confChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn( - &breachTXID, 1, retInfo.breachHeight) + &breachTXID, breachScript, 1, retInfo.breachHeight, + ) if err != nil { brarLog.Errorf("unable to register for conf updates "+ "for txid: %v, err: %v", breachTXID, err) @@ -296,6 +298,7 @@ func convertToSecondLevelRevoke(bo *breachedOutput, breachInfo *retributionInfo, newAmt := spendingTx.TxOut[0].Value bo.amt = btcutil.Amount(newAmt) bo.signDesc.Output.Value = newAmt + bo.signDesc.Output.PkScript = spendingTx.TxOut[0].PkScript // Finally, we'll need to adjust the witness program in the // SignDescriptor. @@ -357,6 +360,7 @@ func (b *breachArbiter) waitForSpendEvent(breachInfo *retributionInfo, var err error spendNtfn, err = b.cfg.Notifier.RegisterSpendNtfn( &breachedOutput.outpoint, + breachedOutput.signDesc.Output.PkScript, breachInfo.breachHeight, ) if err != nil { @@ -556,8 +560,10 @@ justiceTxBroadcast: // notify the caller that initiated the retribution workflow that the // deed has been done. justiceTXID := finalTx.TxHash() + justiceScript := finalTx.TxOut[0].PkScript confChan, err = b.cfg.Notifier.RegisterConfirmationsNtfn( - &justiceTXID, 1, breachConfHeight) + &justiceTXID, justiceScript, 1, breachConfHeight, + ) if err != nil { brarLog.Errorf("unable to register for conf for txid(%v): %v", justiceTXID, err) @@ -720,8 +726,10 @@ func (b *breachArbiter) handleBreachHandoff(breachEvent *ContractBreachEvent) { // confirmed in the chain to ensure we're not dealing with a moving // target. breachTXID := &retInfo.commitHash - cfChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn(breachTXID, 1, - retInfo.breachHeight) + breachScript := retInfo.breachedOutputs[0].signDesc.Output.PkScript + cfChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn( + breachTXID, breachScript, 1, retInfo.breachHeight, + ) if err != nil { brarLog.Errorf("unable to register for conf updates for "+ "txid: %v, err: %v", breachTXID, err) diff --git a/breacharbiter_test.go b/breacharbiter_test.go index 6c13d6687..45dc93f5d 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -18,7 +18,12 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog" + "github.com/btcsuite/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" @@ -27,11 +32,6 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" ) var ( @@ -1012,6 +1012,11 @@ func TestBreachHandoffSuccess(t *testing.T) { ProcessACK: make(chan error, 1), BreachRetribution: &lnwallet.BreachRetribution{ BreachTransaction: bobClose.CloseTx, + LocalOutputSignDesc: &lnwallet.SignDescriptor{ + Output: &wire.TxOut{ + PkScript: breachKeys[0], + }, + }, }, } contractBreaches <- breach @@ -1039,6 +1044,11 @@ func TestBreachHandoffSuccess(t *testing.T) { ProcessACK: make(chan error, 1), BreachRetribution: &lnwallet.BreachRetribution{ BreachTransaction: bobClose.CloseTx, + LocalOutputSignDesc: &lnwallet.SignDescriptor{ + Output: &wire.TxOut{ + PkScript: breachKeys[0], + }, + }, }, } @@ -1083,6 +1093,11 @@ func TestBreachHandoffFail(t *testing.T) { ProcessACK: make(chan error, 1), BreachRetribution: &lnwallet.BreachRetribution{ BreachTransaction: bobClose.CloseTx, + LocalOutputSignDesc: &lnwallet.SignDescriptor{ + Output: &wire.TxOut{ + PkScript: breachKeys[0], + }, + }, }, } contractBreaches <- breach @@ -1130,6 +1145,11 @@ func TestBreachHandoffFail(t *testing.T) { ProcessACK: make(chan error, 1), BreachRetribution: &lnwallet.BreachRetribution{ BreachTransaction: bobClose.CloseTx, + LocalOutputSignDesc: &lnwallet.SignDescriptor{ + Output: &wire.TxOut{ + PkScript: breachKeys[0], + }, + }, }, } diff --git a/chainntnfs/bitcoindnotify/bitcoind.go b/chainntnfs/bitcoindnotify/bitcoind.go index 452a90252..a53141e04 100644 --- a/chainntnfs/bitcoindnotify/bitcoind.go +++ b/chainntnfs/bitcoindnotify/bitcoind.go @@ -577,7 +577,7 @@ type spendCancel struct { // across the 'Spend' channel. The heightHint should represent the earliest // height in the chain where the transaction could have been spent in. func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, - heightHint uint32) (*chainntnfs.SpendEvent, error) { + pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { ntfn := &spendNotification{ targetOutpoint: outpoint, @@ -784,7 +784,7 @@ type confirmationNotification struct { // which will be triggered once the txid reaches numConfs number of // confirmations. func (b *BitcoindNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, - numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { + _ []byte, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { ntfn := &confirmationNotification{ ConfNtfn: chainntnfs.ConfNtfn{ diff --git a/chainntnfs/btcdnotify/btcd.go b/chainntnfs/btcdnotify/btcd.go index fa4f1826c..2fb2f26c4 100644 --- a/chainntnfs/btcdnotify/btcd.go +++ b/chainntnfs/btcdnotify/btcd.go @@ -695,7 +695,7 @@ type spendCancel struct { // across the 'Spend' channel. The heightHint should represent the earliest // height in the chain where the transaction could have been spent in. func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, - heightHint uint32) (*chainntnfs.SpendEvent, error) { + pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { ntfn := &spendNotification{ targetOutpoint: outpoint, @@ -710,6 +710,7 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, case b.notificationRegistry <- ntfn: } + // TODO(roasbeef): update btcd rescan logic to also use both if err := b.chainConn.NotifySpent([]*wire.OutPoint{outpoint}); err != nil { return nil, err } @@ -822,7 +823,7 @@ type confirmationNotification struct { // RegisterConfirmationsNtfn registers a notification with BtcdNotifier // which will be triggered once the txid reaches numConfs number of // confirmations. -func (b *BtcdNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, +func (b *BtcdNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, _ []byte, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { ntfn := &confirmationNotification{ diff --git a/chainntnfs/interface.go b/chainntnfs/interface.go index 57870f64a..ff90cc0a4 100644 --- a/chainntnfs/interface.go +++ b/chainntnfs/interface.go @@ -20,23 +20,27 @@ import ( // resource type ChainNotifier interface { // RegisterConfirmationsNtfn registers an intent to be notified once - // txid reaches numConfs confirmations. The returned ConfirmationEvent - // should properly notify the client once the specified number of - // confirmations has been reached for the txid, as well as if the - // original tx gets re-org'd out of the mainchain. The heightHint - // parameter is provided as a convenience to light clients. The - // heightHint denotes the earliest height in the blockchain in which the - // target txid _could_ have been included in the chain. This can be - // used to bound the search space when checking to see if a - // notification can immediately be dispatched due to historical data. + // txid reaches numConfs confirmations. We also pass in the pkScript as + // the default light client instead needs to match on scripts created + // in the block. The returned ConfirmationEvent should properly notify + // the client once the specified number of confirmations has been + // reached for the txid, as well as if the original tx gets re-org'd + // out of the mainchain. The heightHint parameter is provided as a + // convenience to light clients. The heightHint denotes the earliest + // height in the blockchain in which the target txid _could_ have been + // included in the chain. This can be used to bound the search space + // when checking to see if a notification can immediately be dispatched + // due to historical data. // // NOTE: Dispatching notifications to multiple clients subscribed to // the same (txid, numConfs) tuple MUST be supported. - RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs, + RegisterConfirmationsNtfn(txid *chainhash.Hash, pkScript []byte, numConfs, heightHint uint32) (*ConfirmationEvent, error) // RegisterSpendNtfn registers an intent to be notified once the target - // outpoint is successfully spent within a transaction. The returned + // outpoint is successfully spent within a transaction. The script that + // the outpoint creates must also be specified. This allows this + // interface to be implemented by BIP 158-like filtering. The returned // SpendEvent will receive a send on the 'Spend' transaction once a // transaction spending the input is detected on the blockchain. The // heightHint parameter is provided as a convenience to light clients. @@ -48,7 +52,7 @@ type ChainNotifier interface { // // NOTE: Dispatching notifications to multiple clients subscribed to a // spend of the same outpoint MUST be supported. - RegisterSpendNtfn(outpoint *wire.OutPoint, + RegisterSpendNtfn(outpoint *wire.OutPoint, pkScript []byte, heightHint uint32) (*SpendEvent, error) // RegisterBlockEpochNtfn registers an intent to be notified of each diff --git a/chainntnfs/interface_test.go b/chainntnfs/interface_test.go index c9c83c20a..5bba96812 100644 --- a/chainntnfs/interface_test.go +++ b/chainntnfs/interface_test.go @@ -58,10 +58,10 @@ var ( testAddr = addrPk.AddressPubKeyHash() ) -func getTestTxId(miner *rpctest.Harness) (*chainhash.Hash, error) { +func getTestTxIdAndScript(miner *rpctest.Harness) (*chainhash.Hash, []byte, error) { script, err := txscript.PayToAddrScript(testAddr) if err != nil { - return nil, err + return nil, nil, err } outputs := []*wire.TxOut{ @@ -70,7 +70,13 @@ func getTestTxId(miner *rpctest.Harness) (*chainhash.Hash, error) { PkScript: script, }, } - return miner.SendOutputs(outputs, 10) + + txid, err := miner.SendOutputs(outputs, 10) + if err != nil { + return nil, nil, err + } + + return txid, script, nil } func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error { @@ -116,7 +122,7 @@ func testSingleConfirmationNotification(miner *rpctest.Harness, // We're spending from a coinbase output here, so we use the dedicated // function. - txid, err := getTestTxId(miner) + txid, pkScript, err := getTestTxIdAndScript(miner) if err != nil { t.Fatalf("unable to create test tx: %v", err) } @@ -134,8 +140,9 @@ func testSingleConfirmationNotification(miner *rpctest.Harness, // Now that we have a txid, register a confirmation notification with // the chainntfn source. numConfs := uint32(1) - confIntent, err := notifier.RegisterConfirmationsNtfn(txid, numConfs, - uint32(currentHeight)) + confIntent, err := notifier.RegisterConfirmationsNtfn( + txid, pkScript, numConfs, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register ntfn: %v", err) } @@ -184,7 +191,7 @@ func testMultiConfirmationNotification(miner *rpctest.Harness, // // Again, we'll begin by creating a fresh transaction, so we can obtain // a fresh txid. - txid, err := getTestTxId(miner) + txid, pkScript, err := getTestTxIdAndScript(miner) if err != nil { t.Fatalf("unable to create test addr: %v", err) } @@ -200,8 +207,9 @@ func testMultiConfirmationNotification(miner *rpctest.Harness, } numConfs := uint32(6) - confIntent, err := notifier.RegisterConfirmationsNtfn(txid, numConfs, - uint32(currentHeight)) + confIntent, err := notifier.RegisterConfirmationsNtfn( + txid, pkScript, numConfs, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register ntfn: %v", err) } @@ -242,12 +250,13 @@ func testBatchConfirmationNotification(miner *rpctest.Harness, // verify they're each notified at the proper number of confirmations // below. for i, numConfs := range confSpread { - txid, err := getTestTxId(miner) + txid, pkScript, err := getTestTxIdAndScript(miner) if err != nil { t.Fatalf("unable to create test addr: %v", err) } - confIntent, err := notifier.RegisterConfirmationsNtfn(txid, - numConfs, uint32(currentHeight)) + confIntent, err := notifier.RegisterConfirmationsNtfn( + txid, pkScript, numConfs, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register ntfn: %v", err) } @@ -303,7 +312,7 @@ func testBatchConfirmationNotification(miner *rpctest.Harness, func createSpendableOutput(miner *rpctest.Harness, t *testing.T) (*wire.OutPoint, []byte) { - txid, err := getTestTxId(miner) + txid, _, err := getTestTxIdAndScript(miner) if err != nil { t.Fatalf("unable to create test addr: %v", err) } @@ -411,8 +420,9 @@ func testSpendNotification(miner *rpctest.Harness, const numClients = 5 spendClients := make([]*chainntnfs.SpendEvent, numClients) for i := 0; i < numClients; i++ { - spentIntent, err := notifier.RegisterSpendNtfn(outpoint, - uint32(currentHeight)) + spentIntent, err := notifier.RegisterSpendNtfn( + outpoint, pkScript, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register for spend ntfn: %v", err) } @@ -462,8 +472,9 @@ func testSpendNotification(miner *rpctest.Harness, // Make sure registering a client after the tx is in the mempool still // doesn't trigger a notification. - spentIntent, err := notifier.RegisterSpendNtfn(outpoint, - uint32(currentHeight)) + spentIntent, err := notifier.RegisterSpendNtfn( + outpoint, pkScript, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register for spend ntfn: %v", err) } @@ -554,7 +565,7 @@ func testMultiClientConfirmationNotification(miner *rpctest.Harness, // We'd like to test the case of a multiple clients registered to // receive a confirmation notification for the same transaction. - txid, err := getTestTxId(miner) + txid, pkScript, err := getTestTxIdAndScript(miner) if err != nil { t.Fatalf("unable to create test tx: %v", err) } @@ -578,8 +589,9 @@ func testMultiClientConfirmationNotification(miner *rpctest.Harness, // Register for a conf notification for the above generated txid with // numConfsClients distinct clients. for i := 0; i < numConfsClients; i++ { - confClient, err := notifier.RegisterConfirmationsNtfn(txid, - numConfs, uint32(currentHeight)) + confClient, err := notifier.RegisterConfirmationsNtfn( + txid, pkScript, numConfs, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register for confirmation: %v", err) } @@ -620,7 +632,7 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness, // spending from a coinbase output here, so we use the dedicated // function. - txid3, err := getTestTxId(miner) + txid3, pkScript3, err := getTestTxIdAndScript(miner) if err != nil { t.Fatalf("unable to create test tx: %v", err) } @@ -640,7 +652,7 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness, t.Fatalf("unable to generate block: %v", err) } - txid1, err := getTestTxId(miner) + txid1, pkScript1, err := getTestTxIdAndScript(miner) if err != nil { t.Fatalf("unable to create test tx: %v", err) } @@ -650,7 +662,7 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness, t.Fatalf("tx not relayed to miner: %v", err) } - txid2, err := getTestTxId(miner) + txid2, pkScript2, err := getTestTxIdAndScript(miner) if err != nil { t.Fatalf("unable to create test tx: %v", err) } @@ -675,8 +687,9 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness, // which is included in the last block. The height hint is the height before // the block is included. This notification should fire immediately since // only 1 confirmation is required. - ntfn1, err := notifier.RegisterConfirmationsNtfn(txid1, 1, - uint32(currentHeight)) + ntfn1, err := notifier.RegisterConfirmationsNtfn( + txid1, pkScript1, 1, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register ntfn: %v", err) } @@ -713,8 +726,9 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness, // Register a confirmation notification for tx2, requiring 3 confirmations. // This transaction is only partially confirmed, so the notification should // not fire yet. - ntfn2, err := notifier.RegisterConfirmationsNtfn(txid2, 3, - uint32(currentHeight)) + ntfn2, err := notifier.RegisterConfirmationsNtfn( + txid2, pkScript2, 3, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register ntfn: %v", err) } @@ -740,8 +754,9 @@ func testTxConfirmedBeforeNtfnRegistration(miner *rpctest.Harness, // Finally register a confirmation notification for tx3, requiring 1 // confirmation. Ensure that conf notifications do not refire on txs // 1 or 2. - ntfn3, err := notifier.RegisterConfirmationsNtfn(txid3, 1, - uint32(currentHeight-1)) + ntfn3, err := notifier.RegisterConfirmationsNtfn( + txid3, pkScript3, 1, uint32(currentHeight-1), + ) if err != nil { t.Fatalf("unable to register ntfn: %v", err) } @@ -775,7 +790,7 @@ func testLazyNtfnConsumer(miner *rpctest.Harness, // Create a transaction to be notified about. We'll register for // notifications on this transaction but won't be prompt in checking them - txid, err := getTestTxId(miner) + txid, pkScript, err := getTestTxIdAndScript(miner) if err != nil { t.Fatalf("unable to create test tx: %v", err) } @@ -798,8 +813,9 @@ func testLazyNtfnConsumer(miner *rpctest.Harness, t.Fatalf("unable to generate blocks: %v", err) } - firstConfIntent, err := notifier.RegisterConfirmationsNtfn(txid, numConfs, - uint32(currentHeight)) + firstConfIntent, err := notifier.RegisterConfirmationsNtfn( + txid, pkScript, numConfs, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register ntfn: %v", err) } @@ -812,7 +828,7 @@ func testLazyNtfnConsumer(miner *rpctest.Harness, // Now make another transaction, just because we haven't checked to see // if the first transaction has confirmed doesn't mean that we shouldn't // be able to see if this transaction confirms first - txid, err = getTestTxId(miner) + txid, pkScript, err = getTestTxIdAndScript(miner) if err != nil { t.Fatalf("unable to create test tx: %v", err) } @@ -829,9 +845,9 @@ func testLazyNtfnConsumer(miner *rpctest.Harness, numConfs = 1 - secondConfIntent, err := notifier.RegisterConfirmationsNtfn(txid, numConfs, - uint32(currentHeight)) - + secondConfIntent, err := notifier.RegisterConfirmationsNtfn( + txid, pkScript, numConfs, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register ntfn: %v", err) } @@ -904,8 +920,9 @@ func testSpendBeforeNtfnRegistration(miner *rpctest.Harness, const numClients = 2 spendClients := make([]*chainntnfs.SpendEvent, numClients) for i := 0; i < numClients; i++ { - spentIntent, err := notifier.RegisterSpendNtfn(outpoint, - uint32(currentHeight)) + spentIntent, err := notifier.RegisterSpendNtfn( + outpoint, pkScript, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register for spend ntfn: %v", err) @@ -984,8 +1001,9 @@ func testCancelSpendNtfn(node *rpctest.Harness, const numClients = 2 spendClients := make([]*chainntnfs.SpendEvent, numClients) for i := 0; i < numClients; i++ { - spentIntent, err := notifier.RegisterSpendNtfn(outpoint, - uint32(currentHeight)) + spentIntent, err := notifier.RegisterSpendNtfn( + outpoint, pkScript, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register for spend ntfn: %v", err) } @@ -1150,7 +1168,7 @@ func testReorgConf(miner *rpctest.Harness, notifier chainntnfs.ChainNotifier, t.Fatalf("unable to remove node: %v", err) } - txid, err := getTestTxId(miner) + txid, pkScript, err := getTestTxIdAndScript(miner) if err != nil { t.Fatalf("unable to create test tx: %v", err) } @@ -1168,8 +1186,9 @@ func testReorgConf(miner *rpctest.Harness, notifier chainntnfs.ChainNotifier, // Now that we have a txid, register a confirmation notification with // the chainntfn source. numConfs := uint32(2) - confIntent, err := notifier.RegisterConfirmationsNtfn(txid, numConfs, - uint32(currentHeight)) + confIntent, err := notifier.RegisterConfirmationsNtfn( + txid, pkScript, numConfs, uint32(currentHeight), + ) if err != nil { t.Fatalf("unable to register ntfn: %v", err) } diff --git a/chainntnfs/neutrinonotify/neutrino.go b/chainntnfs/neutrinonotify/neutrino.go index 7d4dc5d0c..f612ef1ce 100644 --- a/chainntnfs/neutrinonotify/neutrino.go +++ b/chainntnfs/neutrinonotify/neutrino.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/gcs/builder" @@ -133,7 +134,7 @@ func (n *NeutrinoNotifier) Start() error { // required that a user MUST set an addr/outpoint/txid when creating a // rescan. To get around this, we'll add a "zero" outpoint, that won't // actually be matched. - var zeroHash chainhash.Hash + var zeroInput neutrino.InputWithScript rescanOptions := []neutrino.RescanOption{ neutrino.StartBlock(startingPoint), neutrino.QuitChan(n.quit), @@ -143,11 +144,12 @@ func (n *NeutrinoNotifier) Start() error { OnFilteredBlockDisconnected: n.onFilteredBlockDisconnected, }, ), - neutrino.WatchTxIDs(zeroHash), + neutrino.WatchInputs(zeroInput), } n.txConfNotifier = chainntnfs.NewTxConfNotifier( - bestHeight, reorgSafetyLimit) + bestHeight, reorgSafetyLimit, + ) // Finally, we'll create our rescan struct, start it, and launch all // the goroutines we need to operate this ChainNotifier instance. @@ -316,8 +318,19 @@ func (n *NeutrinoNotifier) notificationDispatcher() { defer n.wg.Done() confDetails, err := n.historicalConfDetails( - msg.TxID, currentHeight, - msg.heightHint, + msg.TxID, msg.pkScript, currentHeight, msg.heightHint, + ) + if err != nil { + chainntnfs.Log.Error(err) + } + + // We'll map the script into an address + // type so we can instruct neutrino to + // match if the transaction containing + // the script is found in a block. + params := n.p2pNode.ChainParams() + _, addrs, _, err := txscript.ExtractPkScriptAddrs( + msg.pkScript, ¶ms, ) if err != nil { chainntnfs.Log.Error(err) @@ -339,7 +352,7 @@ func (n *NeutrinoNotifier) notificationDispatcher() { // filter so we can be notified of its // future initial confirmation. rescanUpdate := []neutrino.UpdateOption{ - neutrino.AddTxIDs(*msg.TxID), + neutrino.AddAddrs(addrs...), neutrino.Rewind(currentHeight), } err = n.chainView.Update(rescanUpdate...) @@ -410,9 +423,11 @@ func (n *NeutrinoNotifier) notificationDispatcher() { } } -// historicalConfDetails looks up whether a transaction is already included in a -// block in the active chain and, if so, returns details about the confirmation. +// historicalConfDetails looks up whether a transaction is already included in +// a block in the active chain and, if so, returns details about the +// confirmation. func (n *NeutrinoNotifier) historicalConfDetails(targetHash *chainhash.Hash, + pkScript []byte, currentHeight, heightHint uint32) (*chainntnfs.TxConfirmation, error) { // Starting from the height hint, we'll walk forwards in the chain to @@ -437,14 +452,15 @@ func (n *NeutrinoNotifier) historicalConfDetails(targetHash *chainhash.Hash, // With the hash computed, we can now fetch the basic filter // for this height. - regFilter, err := n.p2pNode.GetCFilter(blockHash, - wire.GCSFilterRegular) + regFilter, err := n.p2pNode.GetCFilter( + blockHash, wire.GCSFilterRegular, + ) if err != nil { return nil, fmt.Errorf("unable to retrieve regular filter for "+ "height=%v: %v", scanHeight, err) } - // If the block has no transactions other than the coinbase + // If the block has no transactions other than the Coinbase // transaction, then the filter may be nil, so we'll continue // forward int that case. if regFilter == nil { @@ -452,9 +468,9 @@ func (n *NeutrinoNotifier) historicalConfDetails(targetHash *chainhash.Hash, } // In the case that the filter exists, we'll attempt to see if - // any element in it match our target txid. + // any element in it matches our target public key script. key := builder.DeriveKey(&blockHash) - match, err := regFilter.Match(key, targetHash[:]) + match, err := regFilter.Match(key, pkScript) if err != nil { return nil, fmt.Errorf("unable to query filter: %v", err) } @@ -504,16 +520,15 @@ func (n *NeutrinoNotifier) handleBlockConnected(newBlock *filteredBlock) error { for i, txIn := range mtx.TxIn { prevOut := txIn.PreviousOutPoint - // If this transaction indeed does spend an output which we have a - // registered notification for, then create a spend summary, finally - // sending off the details to the notification subscriber. + // If this transaction indeed does spend an output + // which we have a registered notification for, then + // create a spend summary, finally sending off the + // details to the notification subscriber. clients, ok := n.spendNotifications[prevOut] if !ok { continue } - // TODO(roasbeef): many integration tests expect spend to be - // notified within the mempool. spendDetails := &chainntnfs.SpendDetail{ SpentOutPoint: &prevOut, SpenderTxHash: &txSha, @@ -523,13 +538,16 @@ func (n *NeutrinoNotifier) handleBlockConnected(newBlock *filteredBlock) error { } for _, ntfn := range clients { - chainntnfs.Log.Infof("Dispatching spend notification for "+ - "outpoint=%v", ntfn.targetOutpoint) + chainntnfs.Log.Infof("Dispatching spend "+ + "notification for outpoint=%v", + ntfn.targetOutpoint) + ntfn.spendChan <- spendDetails - // Close spendChan to ensure that any calls to Cancel will not - // block. This is safe to do since the channel is buffered, and - // the message can still be read by the receiver. + // Close spendChan to ensure that any calls to + // Cancel will not block. This is safe to do + // since the channel is buffered, and the + // message can still be read by the receiver. close(ntfn.spendChan) } @@ -537,10 +555,12 @@ func (n *NeutrinoNotifier) handleBlockConnected(newBlock *filteredBlock) error { } } - // A new block has been connected to the main chain. - // Send out any N confirmation notifications which may - // have been triggered by this new block. - n.txConfNotifier.ConnectTip(&newBlock.hash, newBlock.height, newBlock.txns) + // A new block has been connected to the main chain. Send out any N + // confirmation notifications which may have been triggered by this new + // block. + n.txConfNotifier.ConnectTip( + &newBlock.hash, newBlock.height, newBlock.txns, + ) return nil } @@ -592,7 +612,7 @@ type spendCancel struct { // target outpoint has been detected, the details of the spending event will be // sent across the 'Spend' channel. func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, - heightHint uint32) (*chainntnfs.SpendEvent, error) { + pkScript []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { n.heightMtx.RLock() currentHeight := n.bestHeight @@ -651,10 +671,15 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, break } + inputToWatch := neutrino.InputWithScript{ + OutPoint: *outpoint, + PkScript: pkScript, + } + // Before sending off the notification request, we'll attempt to see if // this output is still spent or not at this point in the chain. spendReport, err := n.p2pNode.GetUtxo( - neutrino.WatchOutPoints(*outpoint), + neutrino.WatchInputs(inputToWatch), neutrino.StartBlock(&waddrmgr.BlockStamp{ Height: int32(heightHint), }), @@ -690,7 +715,7 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, // If the output is still unspent, then we'll update our rescan's // filter, and send the request to the dispatcher goroutine. rescanUpdate := []neutrino.UpdateOption{ - neutrino.AddOutPoints(*outpoint), + neutrino.AddInputs(inputToWatch), neutrino.Rewind(currentHeight), } @@ -712,12 +737,14 @@ func (n *NeutrinoNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, type confirmationsNotification struct { chainntnfs.ConfNtfn heightHint uint32 + pkScript []byte } // RegisterConfirmationsNtfn registers a notification with NeutrinoNotifier // which will be triggered once the txid reaches numConfs number of // confirmations. func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, + pkScript []byte, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { ntfn := &confirmationsNotification{ @@ -728,6 +755,7 @@ func (n *NeutrinoNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, Event: chainntnfs.NewConfirmationEvent(numConfs), }, heightHint: heightHint, + pkScript: pkScript, } if err := n.txConfNotifier.Register(&ntfn.ConfNtfn); err != nil { diff --git a/chainparams.go b/chainparams.go index 2751d81ea..812bb6f34 100644 --- a/chainparams.go +++ b/chainparams.go @@ -1,13 +1,13 @@ package main import ( - "github.com/lightningnetwork/lnd/keychain" - litecoinCfg "github.com/ltcsuite/ltcd/chaincfg" - litecoinWire "github.com/ltcsuite/ltcd/wire" "github.com/btcsuite/btcd/chaincfg" bitcoinCfg "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" bitcoinWire "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/keychain" + litecoinCfg "github.com/ltcsuite/ltcd/chaincfg" + litecoinWire "github.com/ltcsuite/ltcd/wire" ) // activeNetParams is a pointer to the parameters specific to the currently diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go index 3083e7252..feb5b2d5e 100644 --- a/channeldb/channel_test.go +++ b/channeldb/channel_test.go @@ -10,16 +10,16 @@ import ( "runtime" "testing" - "github.com/davecgh/go-spew/spew" - "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/shachain" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" _ "github.com/btcsuite/btcwallet/walletdb/bdb" + "github.com/davecgh/go-spew/spew" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/shachain" ) var ( diff --git a/channeldb/graph.go b/channeldb/graph.go index 49b73aad0..d716ead28 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -2,6 +2,7 @@ package channeldb import ( "bytes" + "crypto/sha256" "encoding/binary" "fmt" "image/color" @@ -12,6 +13,7 @@ import ( "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/coreos/bbolt" @@ -2350,12 +2352,62 @@ func (c *ChannelGraph) FetchChannelEdgesByID(chanID uint64) (*ChannelEdgeInfo, * return edgeInfo, policy1, policy2, nil } +// genMultiSigP2WSH generates the p2wsh'd multisig script for 2 of 2 pubkeys. +func genMultiSigP2WSH(aPub, bPub []byte) ([]byte, error) { + if len(aPub) != 33 || len(bPub) != 33 { + return nil, fmt.Errorf("Pubkey size error. Compressed " + + "pubkeys only") + } + + // Swap to sort pubkeys if needed. Keys are sorted in lexicographical + // order. The signatures within the scriptSig must also adhere to the + // order, ensuring that the signatures for each public key appears in + // the proper order on the stack. + if bytes.Compare(aPub, bPub) == 1 { + aPub, bPub = bPub, aPub + } + + // First, we'll generate the witness script for the multi-sig. + bldr := txscript.NewScriptBuilder() + bldr.AddOp(txscript.OP_2) + bldr.AddData(aPub) // Add both pubkeys (sorted). + bldr.AddData(bPub) + bldr.AddOp(txscript.OP_2) + bldr.AddOp(txscript.OP_CHECKMULTISIG) + witnessScript, err := bldr.Script() + if err != nil { + return nil, err + } + + // With the witness script generated, we'll now turn it into a p2sh + // script: + // * OP_0 + bldr = txscript.NewScriptBuilder() + bldr.AddOp(txscript.OP_0) + scriptHash := sha256.Sum256(witnessScript) + bldr.AddData(scriptHash[:]) + + return bldr.Script() +} + +// EdgePoint couples the outpoint of a channel with the funding script that it +// creates. The FilteredChainView will use this to watch for spends of this +// edge point on chain. We require both of these values as depending on the +// concrete implementation, either the pkScript, or the out point will be used. +type EdgePoint struct { + // FundingPkScript is the p2wsh multi-sig script of the target channel. + FundingPkScript []byte + + // OutPoint is the outpoint of the target channel. + OutPoint wire.OutPoint +} + // ChannelView returns the verifiable edge information for each active channel -// within the known channel graph. The set of UTXO's returned are the ones that -// need to be watched on chain to detect channel closes on the resident -// blockchain. -func (c *ChannelGraph) ChannelView() ([]wire.OutPoint, error) { - var chanPoints []wire.OutPoint +// within the known channel graph. The set of UTXO's (along with their scripts) +// returned are the ones that need to be watched on chain to detect channel +// closes on the resident blockchain. +func (c *ChannelGraph) ChannelView() ([]EdgePoint, error) { + var edgePoints []EdgePoint if err := c.db.View(func(tx *bolt.Tx) error { // We're going to iterate over the entire channel index, so // we'll need to fetch the edgeBucket to get to the index as @@ -2368,11 +2420,15 @@ func (c *ChannelGraph) ChannelView() ([]wire.OutPoint, error) { if chanIndex == nil { return ErrGraphNoEdgesFound } + edgeIndex := edges.Bucket(edgeIndexBucket) + if edgeIndex == nil { + return ErrGraphNoEdgesFound + } // Once we have the proper bucket, we'll range over each key // (which is the channel point for the channel) and decode it, // accumulating each entry. - return chanIndex.ForEach(func(chanPointBytes, _ []byte) error { + return chanIndex.ForEach(func(chanPointBytes, chanID []byte) error { chanPointReader := bytes.NewReader(chanPointBytes) var chanPoint wire.OutPoint @@ -2381,14 +2437,33 @@ func (c *ChannelGraph) ChannelView() ([]wire.OutPoint, error) { return err } - chanPoints = append(chanPoints, chanPoint) + edgeInfo, err := fetchChanEdgeInfo( + edgeIndex, chanID, + ) + if err != nil { + return err + } + + pkScript, err := genMultiSigP2WSH( + edgeInfo.BitcoinKey1Bytes[:], + edgeInfo.BitcoinKey2Bytes[:], + ) + if err != nil { + return err + } + + edgePoints = append(edgePoints, EdgePoint{ + FundingPkScript: pkScript, + OutPoint: chanPoint, + }) + return nil }) }); err != nil { return nil, err } - return chanPoints, nil + return edgePoints, nil } // NewChannelEdgePolicy returns a new blank ChannelEdgePolicy. diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go index cc3055b3e..7c8287600 100644 --- a/channeldb/graph_test.go +++ b/channeldb/graph_test.go @@ -997,7 +997,7 @@ func assertNumNodes(t *testing.T, graph *ChannelGraph, n int) { } } -func assertChanViewEqual(t *testing.T, a []wire.OutPoint, b []*wire.OutPoint) { +func assertChanViewEqual(t *testing.T, a []EdgePoint, b []EdgePoint) { if len(a) != len(b) { _, _, line, _ := runtime.Caller(1) t.Fatalf("line %v: chan views don't match", line) @@ -1005,14 +1005,34 @@ func assertChanViewEqual(t *testing.T, a []wire.OutPoint, b []*wire.OutPoint) { chanViewSet := make(map[wire.OutPoint]struct{}) for _, op := range a { - chanViewSet[op] = struct{}{} + chanViewSet[op.OutPoint] = struct{}{} + } + + for _, op := range b { + if _, ok := chanViewSet[op.OutPoint]; !ok { + _, _, line, _ := runtime.Caller(1) + t.Fatalf("line %v: chanPoint(%v) not found in first "+ + "view", line, op) + } + } +} + +func assertChanViewEqualChanPoints(t *testing.T, a []EdgePoint, b []*wire.OutPoint) { + if len(a) != len(b) { + _, _, line, _ := runtime.Caller(1) + t.Fatalf("line %v: chan views don't match", line) + } + + chanViewSet := make(map[wire.OutPoint]struct{}) + for _, op := range a { + chanViewSet[op.OutPoint] = struct{}{} } for _, op := range b { if _, ok := chanViewSet[*op]; !ok { _, _, line, _ := runtime.Caller(1) - t.Fatalf("line %v: chanPoint(%v) not found in first view", - line, op) + t.Fatalf("line %v: chanPoint(%v) not found in first "+ + "view", line, op) } } } @@ -1056,6 +1076,7 @@ func TestGraphPruning(t *testing.T) { // With the vertexes created, we'll next create a series of channels // between them. channelPoints := make([]*wire.OutPoint, 0, numNodes-1) + edgePoints := make([]EdgePoint, 0, numNodes-1) for i := 0; i < numNodes-1; i++ { txHash := sha256.Sum256([]byte{byte(i)}) chanID := uint64(i + 1) @@ -1086,6 +1107,17 @@ func TestGraphPruning(t *testing.T) { t.Fatalf("unable to add node: %v", err) } + pkScript, err := genMultiSigP2WSH( + edgeInfo.BitcoinKey1Bytes[:], edgeInfo.BitcoinKey2Bytes[:], + ) + if err != nil { + t.Fatalf("unable to gen multi-sig p2wsh: %v", err) + } + edgePoints = append(edgePoints, EdgePoint{ + FundingPkScript: pkScript, + OutPoint: op, + }) + // Create and add an edge with random data that points from // node_i -> node_i+1 edge := randEdgePolicy(chanID, op, db) @@ -1113,7 +1145,7 @@ func TestGraphPruning(t *testing.T) { if err != nil { t.Fatalf("unable to get graph channel view: %v", err) } - assertChanViewEqual(t, channelView, channelPoints) + assertChanViewEqual(t, channelView, edgePoints) // Now with our test graph created, we can test the pruning // capabilities of the channel graph. @@ -1145,7 +1177,7 @@ func TestGraphPruning(t *testing.T) { if err != nil { t.Fatalf("unable to get graph channel view: %v", err) } - assertChanViewEqual(t, channelView, channelPoints[2:]) + assertChanViewEqualChanPoints(t, channelView, channelPoints[2:]) // Next we'll create a block that doesn't close any channels within the // graph to test the negative error case. diff --git a/contractcourt/chain_arbitrator.go b/contractcourt/chain_arbitrator.go index f13f65fda..9ab11da87 100644 --- a/contractcourt/chain_arbitrator.go +++ b/contractcourt/chain_arbitrator.go @@ -5,13 +5,13 @@ import ( "sync" "sync/atomic" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" ) // ResolutionMsg is a message sent by resolvers to outside sub-systems once an @@ -384,22 +384,6 @@ func (c *ChainArbitrator) Start() error { // the chain any longer, only resolve the contracts on the confirmed // commitment. for _, closeChanInfo := range closingChannels { - // If this is a pending cooperative close channel then we'll - // simply launch a goroutine to wait until the closing - // transaction has been confirmed. - // TODO(halseth): can remove this since no coop close channels - // should be "pending close" after the recent changes. Keeping - // it for a bit in case someone with a coop close channel in - // the pending close state upgrades to the new commit. - if closeChanInfo.CloseType == channeldb.CooperativeClose { - go c.watchForChannelClose(closeChanInfo) - - // TODO(roasbeef): actually need arb to possibly - // recover from race condition broadcast? - // * if do, can't recover from multi-broadcast - continue - } - blockEpoch, err := c.cfg.Notifier.RegisterBlockEpochNtfn() if err != nil { return err @@ -493,46 +477,6 @@ func (c *ChainArbitrator) Stop() error { return nil } -// watchForChannelClose is used by the ChainArbitrator to watch for the -// ultimate on-chain conformation of an existing cooperative channel closure. -// This is needed if we started a co-op close, but it wasn't fully confirmed -// before we restarted. -// -// NOTE: This must be launched as a goroutine. -func (c *ChainArbitrator) watchForChannelClose(closeInfo *channeldb.ChannelCloseSummary) { - spendNtfn, err := c.cfg.Notifier.RegisterSpendNtfn( - &closeInfo.ChanPoint, closeInfo.CloseHeight, - ) - if err != nil { - log.Errorf("unable to register for spend: %v", err) - return - } - - log.Infof("Waiting for ChannelPoint(%v) to be coop closed on chain", - closeInfo.ChanPoint) - - var ( - commitSpend *chainntnfs.SpendDetail - ok bool - ) - select { - case commitSpend, ok = <-spendNtfn.Spend: - if !ok { - return - } - case <-c.quit: - return - } - - log.Infof("ChannelPoint(%v) is fully closed, at height: %v", - closeInfo.ChanPoint, commitSpend.SpendingHeight) - - if err := c.resolveContract(closeInfo.ChanPoint, nil); err != nil { - log.Errorf("unable to resolve contract: %v", err) - } - -} - // ContractSignals wraps the two signals that affect the state of a channel // being watched by an arbitrator. The two signals we care about are: the // channel has a new set of HTLC's, and the remote party has just broadcast diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index 8126d37d2..04fc86473 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -173,8 +173,21 @@ func (c *chainWatcher) Start() error { heightHint = chanState.FundingBroadcastHeight } + localKey := chanState.LocalChanCfg.MultiSigKey.PubKey.SerializeCompressed() + remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey.SerializeCompressed() + multiSigScript, err := lnwallet.GenMultiSigScript( + localKey, remoteKey, + ) + if err != nil { + return err + } + pkScript, err := lnwallet.WitnessScriptHash(multiSigScript) + if err != nil { + return err + } + spendNtfn, err := c.cfg.notifier.RegisterSpendNtfn( - fundingOut, heightHint, + fundingOut, pkScript, heightHint, ) if err != nil { return err diff --git a/contractcourt/chain_watcher_test.go b/contractcourt/chain_watcher_test.go index a99fcd311..ab0b8f39d 100644 --- a/contractcourt/chain_watcher_test.go +++ b/contractcourt/chain_watcher_test.go @@ -6,18 +6,18 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" ) type mockNotifier struct { spendChan chan *chainntnfs.SpendDetail } -func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs, +func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, _ []byte, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { return nil, nil } @@ -35,7 +35,7 @@ func (m *mockNotifier) Start() error { func (m *mockNotifier) Stop() error { return nil } -func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, +func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, _ []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { return &chainntnfs.SpendEvent{ Spend: m.spendChan, diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 3d96d51bc..8c3689b1d 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -5,12 +5,12 @@ import ( "sync" "sync/atomic" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" - "github.com/btcsuite/btcd/wire" ) const ( diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go index db0f267b6..f17482860 100644 --- a/contractcourt/channel_arbitrator_test.go +++ b/contractcourt/channel_arbitrator_test.go @@ -5,11 +5,11 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" ) type mockChainIO struct{} @@ -18,7 +18,7 @@ func (*mockChainIO) GetBestBlock() (*chainhash.Hash, int32, error) { return nil, 0, nil } -func (*mockChainIO) GetUtxo(op *wire.OutPoint, +func (*mockChainIO) GetUtxo(op *wire.OutPoint, _ []byte, heightHint uint32) (*wire.TxOut, error) { return nil, nil } diff --git a/contractcourt/contract_resolvers.go b/contractcourt/contract_resolvers.go index f81e050d8..086b8d26c 100644 --- a/contractcourt/contract_resolvers.go +++ b/contractcourt/contract_resolvers.go @@ -172,6 +172,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) { // has been spent by a confirmed transaction. spendNtfn, err := h.Notifier.RegisterSpendNtfn( &h.htlcResolution.ClaimOutpoint, + h.htlcResolution.SweepSignDesc.Output.PkScript, h.broadcastHeight, ) if err != nil { @@ -211,8 +212,9 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) { // Otherwise, this is our commitment, so we'll watch for the // second-level transaction to be sufficiently confirmed. secondLevelTXID := h.htlcResolution.SignedTimeoutTx.TxHash() + sweepScript := h.htlcResolution.SignedTimeoutTx.TxOut[0].PkScript confNtfn, err := h.Notifier.RegisterConfirmationsNtfn( - &secondLevelTXID, 1, h.broadcastHeight, + &secondLevelTXID, sweepScript, 1, h.broadcastHeight, ) if err != nil { return nil, err @@ -521,8 +523,9 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) { // With the sweep transaction broadcast, we'll wait for its // confirmation. sweepTXID := h.sweepTx.TxHash() + sweepScript := h.sweepTx.TxOut[0].PkScript confNtfn, err := h.Notifier.RegisterConfirmationsNtfn( - &sweepTXID, 1, h.broadcastHeight, + &sweepTXID, sweepScript, 1, h.broadcastHeight, ) if err != nil { return nil, err @@ -580,7 +583,9 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) { // To wrap this up, we'll wait until the second-level transaction has // been spent, then fully resolve the contract. spendNtfn, err := h.Notifier.RegisterSpendNtfn( - &h.htlcResolution.ClaimOutpoint, h.broadcastHeight, + &h.htlcResolution.ClaimOutpoint, + h.htlcResolution.SweepSignDesc.Output.PkScript, + h.broadcastHeight, ) if err != nil { return nil, err @@ -781,17 +786,34 @@ func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) { // output. If this isn't our commitment transaction, it'll be right on // the resolution. Otherwise, we fetch this pointer from the input of // the time out transaction. - var outPointToWatch wire.OutPoint + var ( + outPointToWatch wire.OutPoint + scriptToWatch []byte + err error + ) if h.htlcResolution.SignedTimeoutTx == nil { outPointToWatch = h.htlcResolution.ClaimOutpoint + scriptToWatch = h.htlcResolution.SweepSignDesc.Output.PkScript } else { + // If this is the remote party's commitment, then we'll need to + // grab watch the output that our timeout transaction points + // to. We can directly grab the outpoint, then also extract the + // witness script (the last element of the witness stack) to + // re-construct the pkScipt we need to watch. outPointToWatch = h.htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint + witness := h.htlcResolution.SignedTimeoutTx.TxIn[0].Witness + scriptToWatch, err = lnwallet.WitnessScriptHash( + witness[len(witness)-1], + ) + if err != nil { + return nil, err + } } // First, we'll register for a spend notification for this output. If - // the remote party sweeps with the pre-image, we'll be notified. + // the remote party sweeps with the pre-image, we'll be notified. spendNtfn, err := h.Notifier.RegisterSpendNtfn( - &outPointToWatch, h.broadcastHeight, + &outPointToWatch, scriptToWatch, h.broadcastHeight, ) if err != nil { return nil, err @@ -800,6 +822,7 @@ func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) { // We'll quickly check to see if the output has already been spent. select { // If the output has already been spent, then we can stop early and + // sweep the pre-image from the output. case commitSpend, ok := <-spendNtfn.Spend: if !ok { return nil, fmt.Errorf("quitting") @@ -1193,8 +1216,9 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { // // TODO(roasbeef): instead sweep asap if remote commit? yeh commitTXID := c.commitResolution.SelfOutPoint.Hash + sweepScript := c.commitResolution.SelfOutputSignDesc.Output.PkScript confNtfn, err := c.Notifier.RegisterConfirmationsNtfn( - &commitTXID, 1, c.broadcastHeight, + &commitTXID, sweepScript, 1, c.broadcastHeight, ) if err != nil { return nil, err @@ -1292,6 +1316,7 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { // until the commitment output has been spent. spendNtfn, err := c.Notifier.RegisterSpendNtfn( &c.commitResolution.SelfOutPoint, + c.commitResolution.SelfOutputSignDesc.Output.PkScript, c.broadcastHeight, ) if err != nil { @@ -1329,8 +1354,9 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) { // Now we'll wait until the sweeping transaction has been fully // confirmed. Once it's confirmed, we can mark this contract resolved. sweepTXID := c.sweepTx.TxHash() + sweepingScript := c.sweepTx.TxOut[0].PkScript confNtfn, err = c.Notifier.RegisterConfirmationsNtfn( - &sweepTXID, 1, c.broadcastHeight, + &sweepTXID, sweepingScript, 1, c.broadcastHeight, ) if err != nil { return nil, err diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index b3880c08c..44edf1874 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -18,6 +18,9 @@ import ( "io/ioutil" "os" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/davecgh/go-spew/spew" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/chainntnfs" @@ -26,9 +29,6 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" ) var ( @@ -258,12 +258,12 @@ func newMockNotifier() *mockNotifier { } func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, - numConfs, _ uint32) (*chainntnfs.ConfirmationEvent, error) { + _ []byte, numConfs, _ uint32) (*chainntnfs.ConfirmationEvent, error) { return nil, nil } -func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, +func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, _ []byte, _ uint32) (*chainntnfs.SpendEvent, error) { return nil, nil } diff --git a/fundingmanager.go b/fundingmanager.go index 7753204dc..fa414545a 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -1752,6 +1752,20 @@ func (f *fundingManager) waitForFundingWithTimeout(completeChan *channeldb.OpenC } } +// makeFundingScript re-creates the funding script for the funding transaction +// of the target channel. +func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) { + localKey := channel.LocalChanCfg.MultiSigKey.PubKey.SerializeCompressed() + remoteKey := channel.RemoteChanCfg.MultiSigKey.PubKey.SerializeCompressed() + + multiSigScript, err := lnwallet.GenMultiSigScript(localKey, remoteKey) + if err != nil { + return nil, err + } + + return lnwallet.WitnessScriptHash(multiSigScript) +} + // 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 @@ -1767,9 +1781,16 @@ func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.Open // Register with the ChainNotifier for a notification once the funding // transaction reaches `numConfs` confirmations. txid := completeChan.FundingOutpoint.Hash + fundingScript, err := makeFundingScript(completeChan) + if err != nil { + fndgLog.Errorf("unable to create funding script for "+ + "ChannelPoint(%v): %v", completeChan.FundingOutpoint, err) + return + } numConfs := uint32(completeChan.NumConfsRequired) - confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn(&txid, - numConfs, completeChan.FundingBroadcastHeight) + confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn( + &txid, fundingScript, numConfs, completeChan.FundingBroadcastHeight, + ) if err != nil { fndgLog.Errorf("Unable to register for confirmation of "+ "ChannelPoint(%v)", completeChan.FundingOutpoint) @@ -2073,8 +2094,16 @@ func (f *fundingManager) annAfterSixConfs(completeChan *channeldb.OpenChannel, shortChanID.ToUint64(), completeChan.FundingOutpoint, numConfs) - confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn(&txid, - numConfs, completeChan.FundingBroadcastHeight) + fundingScript, err := makeFundingScript(completeChan) + if err != nil { + return fmt.Errorf("unable to create funding script for "+ + "ChannelPoint(%v): %v", + completeChan.FundingOutpoint, err) + } + + confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn( + &txid, fundingScript, numConfs, completeChan.FundingBroadcastHeight, + ) if err != nil { return fmt.Errorf("Unable to register for "+ "confirmation of ChannelPoint(%v): %v", diff --git a/fundingmanager_test.go b/fundingmanager_test.go index 7550db4c1..deb08f0f2 100644 --- a/fundingmanager_test.go +++ b/fundingmanager_test.go @@ -98,8 +98,9 @@ type mockNotifier struct { epochChan chan *chainntnfs.BlockEpoch } -func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs, - heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { +func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, + _ []byte, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { + if numConfs == 6 { return &chainntnfs.ConfirmationEvent{ Confirmed: m.sixConfChannel, @@ -125,7 +126,7 @@ func (m *mockNotifier) Stop() error { return nil } -func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, +func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, _ []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { return &chainntnfs.SpendEvent{ Spend: make(chan *chainntnfs.SpendDetail), diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go index b3bcbdb17..9d3a29389 100644 --- a/htlcswitch/mock.go +++ b/htlcswitch/mock.go @@ -13,6 +13,10 @@ import ( "testing" "time" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/fastsha256" "github.com/go-errors/errors" "github.com/lightningnetwork/lightning-onion" @@ -22,10 +26,6 @@ import ( "github.com/lightningnetwork/lnd/lnpeer" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" ) type mockPreimageCache struct { @@ -781,7 +781,7 @@ type mockNotifier struct { epochChan chan *chainntnfs.BlockEpoch } -func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, +func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, _ []byte, numConfs uint32, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { return nil, nil } @@ -800,7 +800,7 @@ func (m *mockNotifier) Stop() error { return nil } -func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, +func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, _ []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { return &chainntnfs.SpendEvent{ diff --git a/lnwallet/btcwallet/blockchain.go b/lnwallet/btcwallet/blockchain.go index 22dee5dd1..62a726505 100644 --- a/lnwallet/btcwallet/blockchain.go +++ b/lnwallet/btcwallet/blockchain.go @@ -9,10 +9,10 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" - "github.com/lightninglabs/neutrino" - "github.com/lightningnetwork/lnd/lnwallet" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/lightninglabs/neutrino" + "github.com/lightningnetwork/lnd/lnwallet" ) var ( @@ -33,15 +33,21 @@ func (b *BtcWallet) GetBestBlock() (*chainhash.Hash, int32, error) { return b.chain.GetBestBlock() } -// GetUtxo returns the original output referenced by the passed outpoint. +// GetUtxo returns the original output referenced by the passed outpoint that +// creates the target pkScript. // // This method is a part of the lnwallet.BlockChainIO interface. -func (b *BtcWallet) GetUtxo(op *wire.OutPoint, heightHint uint32) (*wire.TxOut, error) { +func (b *BtcWallet) GetUtxo(op *wire.OutPoint, pkScript []byte, + heightHint uint32) (*wire.TxOut, error) { + switch backend := b.chain.(type) { case *chain.NeutrinoClient: spendReport, err := backend.CS.GetUtxo( - neutrino.WatchOutPoints(*op), + neutrino.WatchInputs(neutrino.InputWithScript{ + OutPoint: *op, + PkScript: pkScript, + }), neutrino.StartBlock(&waddrmgr.BlockStamp{ Height: int32(heightHint), }), diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index 3829d2961..cc946e428 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -9,8 +9,6 @@ import ( "sync" "time" - "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/lnwallet" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -21,6 +19,8 @@ import ( "github.com/btcsuite/btcwallet/waddrmgr" base "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/walletdb" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnwallet" ) const ( @@ -745,8 +745,24 @@ func (b *BtcWallet) IsSynced() (bool, int64, error) { return false, 0, err } - // If the timestamp no the best header is more than 2 hours in the + // If the timestamp on the best header is more than 2 hours in the // past, then we're not yet synced. minus24Hours := time.Now().Add(-2 * time.Hour) - return !blockHeader.Timestamp.Before(minus24Hours), bestTimestamp, nil + if blockHeader.Timestamp.Before(minus24Hours) { + return false, bestTimestamp, nil + } + + // If this is neutrino, then we'll also want to wait until the set of + // filter headers also match + if neutrinoNode, ok := b.chain.(*chain.NeutrinoClient); ok { + filterDB := neutrinoNode.CS.RegFilterHeaders + _, filterHeaderTip, err := filterDB.ChainTip() + if err != nil { + return false, 0, err + } + + return filterHeaderTip == uint32(bestHeight), bestTimestamp, nil + } + + return true, bestTimestamp, nil } diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 890a3c921..fc80fd42b 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -1427,7 +1427,7 @@ func (lc *LightningChannel) createSignDesc() error { localKey := lc.localChanCfg.MultiSigKey.PubKey.SerializeCompressed() remoteKey := lc.remoteChanCfg.MultiSigKey.PubKey.SerializeCompressed() - multiSigScript, err := genMultiSigScript(localKey, remoteKey) + multiSigScript, err := GenMultiSigScript(localKey, remoteKey) if err != nil { return err } @@ -2043,8 +2043,8 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, htlcRetributions := make([]HtlcRetribution, 0, len(revokedSnapshot.Htlcs)) for _, htlc := range revokedSnapshot.Htlcs { var ( - htlcScript []byte - err error + htlcWitnessScript []byte + err error ) // If the HTLC is dust, then we'll skip it as it doesn't have @@ -2072,7 +2072,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // the sender of the HTLC (relative to us). So we'll // re-generate the sender HTLC script. if htlc.Incoming { - htlcScript, err = senderHTLCScript( + htlcWitnessScript, err = senderHTLCScript( keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, keyRing.RevocationKey, htlc.RHash[:], ) @@ -2084,7 +2084,7 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, // Otherwise, is this was an outgoing HTLC that we // sent, then from the PoV of the remote commitment // state, they're the receiver of this HTLC. - htlcScript, err = receiverHTLCScript( + htlcWitnessScript, err = receiverHTLCScript( htlc.RefundTimeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey, keyRing.RevocationKey, htlc.RHash[:], @@ -2094,13 +2094,19 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64, } } + htlcPkScript, err := WitnessScriptHash(htlcWitnessScript) + if err != nil { + return nil, err + } + htlcRetributions = append(htlcRetributions, HtlcRetribution{ SignDesc: SignDescriptor{ KeyDesc: chanState.LocalChanCfg.RevocationBasePoint, DoubleTweak: commitmentSecret, - WitnessScript: htlcScript, + WitnessScript: htlcWitnessScript, Output: &wire.TxOut{ - Value: int64(htlc.Amt.ToSatoshis()), + PkScript: htlcPkScript, + Value: int64(htlc.Amt.ToSatoshis()), }, HashType: txscript.SigHashAll, }, @@ -5295,7 +5301,8 @@ func newOutgoingHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelCon // With the sign desc created, we can now construct the full witness // for the timeout transaction, and populate it as well. timeoutWitness, err := senderHtlcSpendTimeout( - htlc.Signature, signer, &timeoutSignDesc, timeoutTx) + htlc.Signature, signer, &timeoutSignDesc, timeoutTx, + ) if err != nil { return nil, err } diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 416157fca..0aad560ad 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -237,10 +237,12 @@ type BlockChainIO interface { // GetUtxo attempts to return the passed outpoint if it's still a // member of the utxo set. The passed height hint should be the "birth - // height" of the passed outpoint. In the case that the output is in + // height" of the passed outpoint. The script passed should be the + // script that the outpoint creates. In the case that the output is in // the UTXO set, then the output corresponding to that output is // returned. Otherwise, a non-nil error will be returned. - GetUtxo(op *wire.OutPoint, heightHint uint32) (*wire.TxOut, error) + GetUtxo(op *wire.OutPoint, pkScript []byte, + heightHint uint32) (*wire.TxOut, error) // GetBlockHash returns the hash of the block in the best blockchain // at the given height. diff --git a/lnwallet/interface_test.go b/lnwallet/interface_test.go index 4d1a956af..2aa37c8bd 100644 --- a/lnwallet/interface_test.go +++ b/lnwallet/interface_test.go @@ -1738,7 +1738,7 @@ func testReorgWalletBalance(r *rpctest.Harness, w *lnwallet.LightningWallet, t.Fatalf("unable to synchronize mining nodes: %v", err) } - // Step 3: Do a set of reorgs by disconecting the two miners, mining + // Step 3: Do a set of reorgs by disconnecting the two miners, mining // one block on the passed miner and two on the created miner, // connecting them, and waiting for them to sync. for i := 0; i < 5; i++ { @@ -1918,19 +1918,20 @@ func waitForMempoolTx(r *rpctest.Harness, txid *chainhash.Hash) error { } func waitForWalletSync(r *rpctest.Harness, w *lnwallet.LightningWallet) error { - var synced bool - var err error - var bestHash, knownHash *chainhash.Hash - var bestHeight, knownHeight int32 - timeout := time.After(10 * time.Second) + var ( + synced bool + err error + bestHash, knownHash *chainhash.Hash + bestHeight, knownHeight int32 + ) + timeout := time.After(30 * time.Second) for !synced { // Do a short wait select { case <-timeout: - return fmt.Errorf("timeout after 10s") - default: + return fmt.Errorf("timeout after 30s") + case <-time.Tick(50 * time.Millisecond): } - time.Sleep(100 * time.Millisecond) // Check whether the chain source of the wallet is caught up to // the harness it's supposed to be catching up to. diff --git a/lnwallet/script_utils.go b/lnwallet/script_utils.go index 5afe28395..3c7e08f66 100644 --- a/lnwallet/script_utils.go +++ b/lnwallet/script_utils.go @@ -57,9 +57,9 @@ func WitnessScriptHash(witnessScript []byte) ([]byte, error) { return bldr.Script() } -// genMultiSigScript generates the non-p2sh'd multisig script for 2 of 2 +// GenMultiSigScript generates the non-p2sh'd multisig script for 2 of 2 // pubkeys. -func genMultiSigScript(aPub, bPub []byte) ([]byte, error) { +func GenMultiSigScript(aPub, bPub []byte) ([]byte, error) { if len(aPub) != 33 || len(bPub) != 33 { return nil, fmt.Errorf("Pubkey size error. Compressed pubkeys only") } @@ -91,7 +91,7 @@ func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, erro } // First, create the 2-of-2 multi-sig script itself. - witnessScript, err := genMultiSigScript(aPub, bPub) + witnessScript, err := GenMultiSigScript(aPub, bPub) if err != nil { return nil, nil, err } diff --git a/lnwallet/script_utils_test.go b/lnwallet/script_utils_test.go index 0bfac97a5..7adacba47 100644 --- a/lnwallet/script_utils_test.go +++ b/lnwallet/script_utils_test.go @@ -8,12 +8,12 @@ import ( "testing" "time" - "github.com/lightningnetwork/lnd/keychain" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/keychain" ) // TestCommitmentSpendValidation test the spendability of both outputs within diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 0dcc4cc39..8a2d9b1f2 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -8,20 +8,20 @@ import ( "sync" "sync/atomic" + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcutil/hdkeychain" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" - "github.com/btcsuite/btcd/blockchain" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcutil/hdkeychain" - "github.com/lightningnetwork/lnd/shachain" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/txsort" + "github.com/lightningnetwork/lnd/shachain" ) const ( @@ -262,15 +262,15 @@ type LightningWallet struct { // is removed from limbo. Each reservation is tracked by a unique // monotonically integer. All requests concerning the channel MUST // carry a valid, active funding ID. - fundingLimbo map[uint64]*ChannelReservation - limboMtx sync.RWMutex + fundingLimbo map[uint64]*ChannelReservation + limboMtx sync.RWMutex // lockedOutPoints is a set of the currently locked outpoint. This // information is kept in order to provide an easy way to unlock all // the currently locked outpoints. lockedOutPoints map[wire.OutPoint]struct{} - quit chan struct{} + quit chan struct{} wg sync.WaitGroup @@ -971,8 +971,18 @@ func (l *LightningWallet) handleFundingCounterPartySigs(msg *addCounterPartySigs // Fetch the alleged previous output along with the // pkscript referenced by this input. - // TODO(roasbeef): when dual funder pass actual height-hint - output, err := l.Cfg.ChainIO.GetUtxo(&txin.PreviousOutPoint, 0) + // + // TODO(roasbeef): when dual funder pass actual + // height-hint + pkScript, err := WitnessScriptHash( + txin.Witness[len(txin.Witness)-1], + ) + if err != nil { + } + output, err := l.Cfg.ChainIO.GetUtxo( + &txin.PreviousOutPoint, + pkScript, 0, + ) if output == nil { msg.err <- fmt.Errorf("input to funding tx "+ "does not exist: %v", err) diff --git a/mock.go b/mock.go index e7b418286..cd0461e8c 100644 --- a/mock.go +++ b/mock.go @@ -5,15 +5,15 @@ import ( "fmt" "sync" - "github.com/lightningnetwork/lnd/chainntnfs" - "github.com/lightningnetwork/lnd/keychain" - "github.com/lightningnetwork/lnd/lnwallet" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnwallet" ) // The block height returned by the mock BlockChainIO's GetBestBlock. @@ -85,8 +85,8 @@ type mockNotfier struct { confChannel chan *chainntnfs.TxConfirmation } -func (m *mockNotfier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs, - heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { +func (m *mockNotfier) RegisterConfirmationsNtfn(txid *chainhash.Hash, + _ []byte, numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { return &chainntnfs.ConfirmationEvent{ Confirmed: m.confChannel, }, nil @@ -105,7 +105,7 @@ func (m *mockNotfier) Start() error { func (m *mockNotfier) Stop() error { return nil } -func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint, +func (m *mockNotfier) RegisterSpendNtfn(outpoint *wire.OutPoint, _ []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { return &chainntnfs.SpendEvent{ Spend: make(chan *chainntnfs.SpendDetail), @@ -131,7 +131,7 @@ func makeMockSpendNotifier() *mockSpendNotifier { } func (m *mockSpendNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, - heightHint uint32) (*chainntnfs.SpendEvent, error) { + _ []byte, heightHint uint32) (*chainntnfs.SpendEvent, error) { m.mtx.Lock() defer m.mtx.Unlock() @@ -172,7 +172,7 @@ func (*mockChainIO) GetBestBlock() (*chainhash.Hash, int32, error) { return activeNetParams.GenesisHash, fundingBroadcastHeight, nil } -func (*mockChainIO) GetUtxo(op *wire.OutPoint, +func (*mockChainIO) GetUtxo(op *wire.OutPoint, _ []byte, heightHint uint32) (*wire.TxOut, error) { return nil, nil } diff --git a/peer.go b/peer.go index 987f147a0..c4cad2280 100644 --- a/peer.go +++ b/peer.go @@ -1837,7 +1837,7 @@ func (p *peer) finalizeChanClosure(chanCloser *channelCloser) { } go waitForChanToClose(chanCloser.negotiationHeight, notifier, errChan, - chanPoint, &closingTxid, func() { + chanPoint, &closingTxid, closingTx.TxOut[0].PkScript, func() { // Respond to the local subsystem which requested the // channel closure. if closeReq != nil { @@ -1872,15 +1872,16 @@ func (p *peer) finalizeChanClosure(chanCloser *channelCloser) { // the function, then it will be sent over the errChan. func waitForChanToClose(bestHeight uint32, notifier chainntnfs.ChainNotifier, errChan chan error, chanPoint *wire.OutPoint, - closingTxID *chainhash.Hash, cb func()) { + closingTxID *chainhash.Hash, closeScript []byte, cb func()) { peerLog.Infof("Waiting for confirmation of cooperative close of "+ "ChannelPoint(%v) with txid: %v", chanPoint, closingTxID) // TODO(roasbeef): add param for num needed confs - confNtfn, err := notifier.RegisterConfirmationsNtfn(closingTxID, 1, - bestHeight) + confNtfn, err := notifier.RegisterConfirmationsNtfn( + closingTxID, closeScript, 1, bestHeight, + ) if err != nil { if errChan != nil { errChan <- err diff --git a/routing/chainview/bitcoind.go b/routing/chainview/bitcoind.go index c4014dce4..7996c472a 100644 --- a/routing/chainview/bitcoind.go +++ b/routing/chainview/bitcoind.go @@ -13,9 +13,9 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/lightningnetwork/lnd/channeldb" ) // BitcoindFilteredChainView is an implementation of the FilteredChainView @@ -66,6 +66,7 @@ var _ FilteredChainView = (*BitcoindFilteredChainView)(nil) func NewBitcoindFilteredChainView(config rpcclient.ConnConfig, zmqConnect string, params chaincfg.Params) (*BitcoindFilteredChainView, error) { + chainView := &BitcoindFilteredChainView{ chainFilter: make(map[wire.OutPoint]struct{}), filterUpdates: make(chan filterUpdate), @@ -308,7 +309,6 @@ func (b *BitcoindFilteredChainView) chainFilterer() { // filter, so we'll apply the update, possibly rewinding our // state partially. case update := <-b.filterUpdates: - // First, we'll add all the new UTXO's to the set of // watched UTXO's, eliminating any duplicates in the // process. @@ -325,8 +325,9 @@ func (b *BitcoindFilteredChainView) chainFilterer() { // will cause all following notifications from and // calls to it return blocks filtered with the new // filter. - b.chainClient.LoadTxFilter(false, []btcutil.Address{}, - update.newUtxos) + b.chainClient.LoadTxFilter( + false, update.newUtxos, + ) // All blocks gotten after we loaded the filter will // have the filter applied, but we will need to rescan @@ -362,7 +363,8 @@ func (b *BitcoindFilteredChainView) chainFilterer() { // block at a time, skipping blocks that might // have gone missing. rescanned, err := b.chainClient.RescanBlocks( - []chainhash.Hash{*blockHash}) + []chainhash.Hash{*blockHash}, + ) if err != nil { log.Warnf("Unable to rescan block "+ "with hash %v at height %d: %v", @@ -379,7 +381,8 @@ func (b *BitcoindFilteredChainView) chainFilterer() { continue } decoded, err := decodeJSONBlock( - &rescanned[0], i) + &rescanned[0], i, + ) if err != nil { log.Errorf("Unable to decode block: %v", err) @@ -421,9 +424,12 @@ func (b *BitcoindFilteredChainView) chainFilterer() { // We've received a new event from the chain client. case event := <-b.chainClient.Notifications(): switch e := event.(type) { + case chain.FilteredBlockConnected: - b.onFilteredBlockConnected(e.Block.Height, - e.Block.Hash, e.RelevantTxs) + b.onFilteredBlockConnected( + e.Block.Height, e.Block.Hash, e.RelevantTxs, + ) + case chain.BlockDisconnected: b.onFilteredBlockDisconnected(e.Height, e.Hash) } @@ -442,11 +448,18 @@ func (b *BitcoindFilteredChainView) chainFilterer() { // rewound to ensure all relevant notifications are dispatched. // // NOTE: This is part of the FilteredChainView interface. -func (b *BitcoindFilteredChainView) UpdateFilter(ops []wire.OutPoint, updateHeight uint32) error { +func (b *BitcoindFilteredChainView) UpdateFilter(ops []channeldb.EdgePoint, + updateHeight uint32) error { + + newUtxos := make([]wire.OutPoint, len(ops)) + for i, op := range ops { + newUtxos[i] = op.OutPoint + } + select { case b.filterUpdates <- filterUpdate{ - newUtxos: ops, + newUtxos: newUtxos, updateHeight: updateHeight, }: return nil diff --git a/routing/chainview/btcd.go b/routing/chainview/btcd.go index fc232f612..34d106b30 100644 --- a/routing/chainview/btcd.go +++ b/routing/chainview/btcd.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/channeldb" ) // BtcdFilteredChainView is an implementation of the FilteredChainView @@ -447,11 +448,18 @@ type filterUpdate struct { // rewound to ensure all relevant notifications are dispatched. // // NOTE: This is part of the FilteredChainView interface. -func (b *BtcdFilteredChainView) UpdateFilter(ops []wire.OutPoint, updateHeight uint32) error { +func (b *BtcdFilteredChainView) UpdateFilter(ops []channeldb.EdgePoint, + updateHeight uint32) error { + + newUtxos := make([]wire.OutPoint, len(ops)) + for i, op := range ops { + newUtxos[i] = op.OutPoint + } + select { case b.filterUpdates <- filterUpdate{ - newUtxos: ops, + newUtxos: newUtxos, updateHeight: updateHeight, }: return nil diff --git a/routing/chainview/interface.go b/routing/chainview/interface.go index 036d90c0a..cfa9fccf4 100644 --- a/routing/chainview/interface.go +++ b/routing/chainview/interface.go @@ -3,6 +3,7 @@ package chainview import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/channeldb" ) // FilteredChainView represents a subscription to a certain subset of the @@ -42,7 +43,7 @@ type FilteredChainView interface { // relevant notifications are dispatched, meaning blocks with a height // lower than the best known height might be sent over the // FilteredBlocks() channel. - UpdateFilter(ops []wire.OutPoint, updateHeight uint32) error + UpdateFilter(ops []channeldb.EdgePoint, updateHeight uint32) error // FilterBlock takes a block hash, and returns a FilteredBlocks which // is the result of applying the current registered UTXO sub-set on the diff --git a/routing/chainview/interface_test.go b/routing/chainview/interface_test.go index c563434a9..bb8ef1afd 100644 --- a/routing/chainview/interface_test.go +++ b/routing/chainview/interface_test.go @@ -21,6 +21,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/lightninglabs/neutrino" + "github.com/lightningnetwork/lnd/channeldb" "github.com/ltcsuite/ltcd/btcjson" "github.com/btcsuite/btcwallet/walletdb" @@ -238,8 +239,11 @@ func testFilterBlockNotifications(node *rpctest.Harness, t.Fatalf("unable to get current height: %v", err) } - // Now we'll add both output to the current filter. - filter := []wire.OutPoint{*outPoint1, *outPoint2} + // Now we'll add both outpoints to the current filter. + filter := []channeldb.EdgePoint{ + {targetScript, *outPoint1}, + {targetScript, *outPoint2}, + } err = chainView.UpdateFilter(filter, uint32(currentHeight)) if err != nil { t.Fatalf("unable to update filter: %v", err) @@ -382,8 +386,9 @@ func testUpdateFilterBackTrack(node *rpctest.Harness, // After the block has been mined+notified we'll update the filter with // a _prior_ height so a "rewind" occurs. - filter := []wire.OutPoint{*outPoint} - + filter := []channeldb.EdgePoint{ + {testScript, *outPoint}, + } err = chainView.UpdateFilter(filter, uint32(currentHeight)) if err != nil { t.Fatalf("unable to update filter: %v", err) @@ -496,7 +501,10 @@ func testFilterSingleBlock(node *rpctest.Harness, chainView FilteredChainView, // Now we'll manually trigger filtering the block generated above. // First, we'll add the two outpoints to our filter. - filter := []wire.OutPoint{*outPoint1, *outPoint2} + filter := []channeldb.EdgePoint{ + {testScript, *outPoint1}, + {testScript, *outPoint2}, + } err = chainView.UpdateFilter(filter, uint32(currentHeight)) if err != nil { t.Fatalf("unable to update filter: %v", err) @@ -562,6 +570,13 @@ func testFilterBlockDisconnected(node *rpctest.Harness, newBlocks := reorgView.FilteredBlocks() disconnectedBlocks := reorgView.DisconnectedBlocks() + // If this the neutrino backend, then we'll give it some time to catch + // up, as it's a bit slower to consume new blocks compared to the RPC + // backends. + if _, ok := reorgView.(*CfFilteredChainView); ok { + time.Sleep(time.Second * 3) + } + _, oldHeight, err := reorgNode.Node.GetBestBlock() if err != nil { t.Fatalf("unable to get current height: %v", err) @@ -591,7 +606,8 @@ func testFilterBlockDisconnected(node *rpctest.Harness, case block := <-newBlocks: if i < oldHeight { t.Fatalf("did not expect to get new block "+ - "in iteration %d", i) + "in iteration %d, old height: %v", i, + oldHeight) } expectedHeight := uint32(i - oldHeight + 1) if block.Height != expectedHeight { diff --git a/routing/chainview/neutrino.go b/routing/chainview/neutrino.go index 53e71ba39..56d16708b 100644 --- a/routing/chainview/neutrino.go +++ b/routing/chainview/neutrino.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcutil/gcs/builder" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/lightninglabs/neutrino" + "github.com/lightningnetwork/lnd/channeldb" ) // CfFilteredChainView is an implementation of the FilteredChainView interface @@ -96,7 +97,7 @@ func (c *CfFilteredChainView) Start() error { // required that an user MUST set a addr/outpoint/txid when creating a // rescan. To get around this, we'll add a "zero" outpoint, that won't // actually be matched. - var zeroPoint wire.OutPoint + var zeroPoint neutrino.InputWithScript rescanOptions := []neutrino.RescanOption{ neutrino.StartBlock(startingPoint), neutrino.QuitChan(c.quit), @@ -106,7 +107,7 @@ func (c *CfFilteredChainView) Start() error { OnFilteredBlockDisconnected: c.onFilteredBlockDisconnected, }, ), - neutrino.WatchOutPoints(zeroPoint), + neutrino.WatchInputs(zeroPoint), } // Finally, we'll create our rescan struct, start it, and launch all @@ -314,7 +315,7 @@ func (c *CfFilteredChainView) FilterBlock(blockHash *chainhash.Hash) (*FilteredB // rewound to ensure all relevant notifications are dispatched. // // NOTE: This is part of the FilteredChainView interface. -func (c *CfFilteredChainView) UpdateFilter(ops []wire.OutPoint, +func (c *CfFilteredChainView) UpdateFilter(ops []channeldb.EdgePoint, updateHeight uint32) error { log.Debugf("Updating chain filter with new UTXO's: %v", ops) @@ -323,14 +324,22 @@ func (c *CfFilteredChainView) UpdateFilter(ops []wire.OutPoint, // UTXO's, ignoring duplicates in the process. c.filterMtx.Lock() for _, op := range ops { - c.chainFilter[op] = builder.OutPointToFilterEntry(op) + c.chainFilter[op.OutPoint] = op.FundingPkScript } c.filterMtx.Unlock() + inputs := make([]neutrino.InputWithScript, len(ops)) + for i, op := range ops { + inputs[i] = neutrino.InputWithScript{ + PkScript: op.FundingPkScript, + OutPoint: op.OutPoint, + } + } + // With our internal chain view update, we'll craft a new update to the // chainView which includes our new UTXO's, and current update height. rescanUpdate := []neutrino.UpdateOption{ - neutrino.AddOutPoints(ops...), + neutrino.AddInputs(inputs...), neutrino.Rewind(updateHeight), neutrino.DisableDisconnectedNtfns(true), } diff --git a/routing/notifications_test.go b/routing/notifications_test.go index 40d6cab8e..dbd9467a9 100644 --- a/routing/notifications_test.go +++ b/routing/notifications_test.go @@ -10,15 +10,15 @@ import ( prand "math/rand" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/chainview" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" ) var ( @@ -176,7 +176,7 @@ func (m *mockChain) addUtxo(op wire.OutPoint, out *wire.TxOut) { m.utxos[op] = *out m.Unlock() } -func (m *mockChain) GetUtxo(op *wire.OutPoint, _ uint32) (*wire.TxOut, error) { +func (m *mockChain) GetUtxo(op *wire.OutPoint, _ []byte, _ uint32) (*wire.TxOut, error) { m.RLock() defer m.RUnlock() @@ -242,12 +242,12 @@ func (m *mockChainView) Reset() { m.staleBlocks = make(chan *chainview.FilteredBlock, 10) } -func (m *mockChainView) UpdateFilter(ops []wire.OutPoint, updateHeight uint32) error { +func (m *mockChainView) UpdateFilter(ops []channeldb.EdgePoint, updateHeight uint32) error { m.Lock() defer m.Unlock() for _, op := range ops { - m.filter[op] = struct{}{} + m.filter[op.OutPoint] = struct{}{} } return nil diff --git a/routing/router.go b/routing/router.go index dcef3a283..888588654 100644 --- a/routing/router.go +++ b/routing/router.go @@ -967,7 +967,7 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error { // to obtain the full funding outpoint that's encoded within // the channel ID. channelID := lnwire.NewShortChanIDFromInt(msg.ChannelID) - fundingPoint, err := r.fetchChanPoint(&channelID) + fundingPoint, _, err := r.fetchChanPoint(&channelID) if err != nil { r.rejectMtx.Lock() r.rejectCache[msg.ChannelID] = struct{}{} @@ -977,11 +977,25 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error { "chan_id=%v: %v", msg.ChannelID, err) } + // Recreate witness output to be sure that declared in channel + // edge bitcoin keys and channel value corresponds to the + // reality. + witnessScript, err := lnwallet.GenMultiSigScript( + msg.BitcoinKey1Bytes[:], msg.BitcoinKey2Bytes[:], + ) + if err != nil { + return err + } + fundingPkScript, err := lnwallet.WitnessScriptHash(witnessScript) + if err != nil { + return err + } + // Now that we have the funding outpoint of the channel, ensure // that it hasn't yet been spent. If so, then this channel has // been closed so we'll ignore it. chanUtxo, err := r.cfg.Chain.GetUtxo( - fundingPoint, channelID.BlockHeight, + fundingPoint, fundingPkScript, channelID.BlockHeight, ) if err != nil { r.rejectMtx.Lock() @@ -993,26 +1007,14 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error { fundingPoint, err) } - // Recreate witness output to be sure that declared in channel - // edge bitcoin keys and channel value corresponds to the - // reality. - _, witnessOutput, err := lnwallet.GenFundingPkScript( - msg.BitcoinKey1Bytes[:], msg.BitcoinKey2Bytes[:], - chanUtxo.Value, - ) - if err != nil { - return errors.Errorf("unable to create funding pk "+ - "script: %v", err) - } - // By checking the equality of witness pkscripts we checks that // funding witness script is multisignature lock which contains // both local and remote public keys which was declared in // channel edge and also that the announced channel value is // right. - if !bytes.Equal(witnessOutput.PkScript, chanUtxo.PkScript) { + if !bytes.Equal(fundingPkScript, chanUtxo.PkScript) { return errors.Errorf("pkScript mismatch: expected %x, "+ - "got %x", witnessOutput.PkScript, chanUtxo.PkScript) + "got %x", fundingPkScript, chanUtxo.PkScript) } // TODO(roasbeef): this is a hack, needs to be removed @@ -1034,7 +1036,12 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error { // update the current UTXO filter within our active // FilteredChainView so we are notified if/when this channel is // closed. - filterUpdate := []wire.OutPoint{*fundingPoint} + filterUpdate := []channeldb.EdgePoint{ + { + FundingPkScript: fundingPkScript, + OutPoint: *fundingPoint, + }, + } err = r.cfg.ChainView.UpdateFilter( filterUpdate, atomic.LoadUint32(&r.bestHeight), ) @@ -1105,7 +1112,9 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error { // Before we can update the channel information, we'll // ensure that the target channel is still open by // querying the utxo-set for its existence. - chanPoint, err := r.fetchChanPoint(&channelID) + chanPoint, fundingPkScript, err := r.fetchChanPoint( + &channelID, + ) if err != nil { r.rejectMtx.Lock() r.rejectCache[msg.ChannelID] = struct{}{} @@ -1116,7 +1125,7 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error { msg.ChannelID, err) } _, err = r.cfg.Chain.GetUtxo( - chanPoint, channelID.BlockHeight, + chanPoint, fundingPkScript, channelID.BlockHeight, ) if err != nil { r.rejectMtx.Lock() @@ -1157,21 +1166,22 @@ func (r *ChannelRouter) processUpdate(msg interface{}) error { } // fetchChanPoint retrieves the original outpoint which is encoded within the -// channelID. +// channelID. This method also return the public key script for the target +// transaction. // // TODO(roasbeef): replace with call to GetBlockTransaction? (would allow to // later use getblocktxn) -func (r *ChannelRouter) fetchChanPoint(chanID *lnwire.ShortChannelID) (*wire.OutPoint, error) { +func (r *ChannelRouter) fetchChanPoint(chanID *lnwire.ShortChannelID) (*wire.OutPoint, []byte, error) { // First fetch the block hash by the block number encoded, then use // that hash to fetch the block itself. blockNum := int64(chanID.BlockHeight) blockHash, err := r.cfg.Chain.GetBlockHash(blockNum) if err != nil { - return nil, err + return nil, nil, err } fundingBlock, err := r.cfg.Chain.GetBlock(blockHash) if err != nil { - return nil, err + return nil, nil, err } // As a sanity check, ensure that the advertised transaction index is @@ -1179,7 +1189,7 @@ func (r *ChannelRouter) fetchChanPoint(chanID *lnwire.ShortChannelID) (*wire.Out // block. numTxns := uint32(len(fundingBlock.Transactions)) if chanID.TxIndex > numTxns-1 { - return nil, fmt.Errorf("tx_index=#%v is out of range "+ + return nil, nil, fmt.Errorf("tx_index=#%v is out of range "+ "(max_index=%v), network_chan_id=%v\n", chanID.TxIndex, numTxns-1, spew.Sdump(chanID)) } @@ -1190,7 +1200,7 @@ func (r *ChannelRouter) fetchChanPoint(chanID *lnwire.ShortChannelID) (*wire.Out return &wire.OutPoint{ Hash: fundingTx.TxHash(), Index: uint32(chanID.TxPosition), - }, nil + }, fundingTx.TxOut[chanID.TxPosition].PkScript, nil } // routingMsg couples a routing related routing topology update to the diff --git a/rpcserver.go b/rpcserver.go index 9261debe8..42e27b412 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1083,7 +1083,7 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest, errChan = make(chan error, 1) notifier := r.server.cc.chainNotifier go waitForChanToClose(uint32(bestHeight), notifier, errChan, chanPoint, - &closingTxid, func() { + &closingTxid, closingTx.TxOut[0].PkScript, func() { // Respond to the local subsystem which // requested the channel closure. updateChan <- &lnrpc.CloseStatusUpdate{ diff --git a/test_utils.go b/test_utils.go index c44b4b43f..0de7f0f80 100644 --- a/test_utils.go +++ b/test_utils.go @@ -10,6 +10,10 @@ import ( "net" "os" + "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/contractcourt" @@ -18,10 +22,6 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/shachain" - "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" ) var ( diff --git a/utxonursery.go b/utxonursery.go index 3aac57ed7..ec2424d3e 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -1145,7 +1145,9 @@ func (u *utxoNursery) registerSweepConf(finalTx *wire.MsgTx, finalTxID := finalTx.TxHash() confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn( - &finalTxID, u.cfg.ConfDepth, heightHint) + &finalTxID, finalTx.TxOut[0].PkScript, u.cfg.ConfDepth, + heightHint, + ) if err != nil { utxnLog.Errorf("unable to register notification for "+ "sweep confirmation: %v", finalTxID) @@ -1251,7 +1253,9 @@ func (u *utxoNursery) registerTimeoutConf(baby *babyOutput, heightHint uint32) e // Register for the confirmation of presigned htlc txn. confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn( - &birthTxID, u.cfg.ConfDepth, heightHint) + &birthTxID, baby.timeoutTx.TxOut[0].PkScript, u.cfg.ConfDepth, + heightHint, + ) if err != nil { return err } @@ -1316,8 +1320,10 @@ func (u *utxoNursery) registerPreschoolConf(kid *kidOutput, heightHint uint32) e // de-duplicate // * need to do above? - confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn(&txID, - u.cfg.ConfDepth, heightHint) + pkScript := kid.signDesc.Output.PkScript + confChan, err := u.cfg.Notifier.RegisterConfirmationsNtfn( + &txID, pkScript, u.cfg.ConfDepth, heightHint, + ) if err != nil { return err }