diff --git a/channeldb/channel.go b/channeldb/channel.go index 9e13e5d34..12e942be7 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -1578,9 +1578,9 @@ const ( ) // ChannelCloseSummary contains the final state of a channel at the point it -// was closed. Once a channel is closed, all the information pertaining to -// that channel within the openChannelBucket is deleted, and a compact -// summary is put in place instead. +// was closed. Once a channel is closed, all the information pertaining to that +// channel within the openChannelBucket is deleted, and a compact summary is +// put in place instead. type ChannelCloseSummary struct { // ChanPoint is the outpoint for this channel's funding transaction, // and is used as a unique identifier for the channel. @@ -1606,7 +1606,8 @@ type ChannelCloseSummary struct { // Capacity was the total capacity of the channel. Capacity btcutil.Amount - // CloseHeight is the height at which the funding transaction was spent. + // CloseHeight is the height at which the funding transaction was + // spent. CloseHeight uint32 // SettledBalance is our total balance settled balance at the time of @@ -1636,6 +1637,21 @@ type ChannelCloseSummary struct { // closed, they'll stay marked as "pending" until _all_ the pending // funds have been swept. IsPending bool + + // RemoteCurrentRevocation is the current revocation for their + // commitment transaction. However, since this the derived public key, + // we don't yet have the private key so we aren't yet able to verify + // that it's actually in the hash chain. + RemoteCurrentRevocation *btcec.PublicKey + + // RemoteNextRevocation is the revocation key to be used for the *next* + // commitment transaction we create for the local node. Within the + // specification, this value is referred to as the + // per-commitment-point. + RemoteNextRevocation *btcec.PublicKey + + // LocalChanCfg is the channel configuration for the local node. + LocalChanConfig ChannelConfig } // CloseChannel closes a previously active Lightning channel. Closing a channel @@ -1675,6 +1691,16 @@ func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary) error { return ErrNoActiveChannels } + // Before we delete the channel state, we'll read out the full + // details, as we'll also store portions of this information + // for record keeping. + chanState, err := fetchOpenChannel( + chanBucket, &c.FundingOutpoint, + ) + if err != nil { + return err + } + // Now that the index to this channel has been deleted, purge // the remaining channel metadata from the database. err = deleteOpenChannel(chanBucket, chanPointBuf.Bytes()) @@ -1703,7 +1729,9 @@ func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary) error { // Finally, create a summary of this channel in the closed // channel bucket for this node. - return putChannelCloseSummary(tx, chanPointBuf.Bytes(), summary) + return putChannelCloseSummary( + tx, chanPointBuf.Bytes(), summary, chanState, + ) }) } @@ -1818,13 +1846,17 @@ func (c *OpenChannel) RemoteRevocationStore() (shachain.Store, error) { } func putChannelCloseSummary(tx *bolt.Tx, chanID []byte, - summary *ChannelCloseSummary) error { + summary *ChannelCloseSummary, lastChanState *OpenChannel) error { closedChanBucket, err := tx.CreateBucketIfNotExists(closedChannelBucket) if err != nil { return err } + summary.RemoteCurrentRevocation = lastChanState.RemoteCurrentRevocation + summary.RemoteNextRevocation = lastChanState.RemoteNextRevocation + summary.LocalChanConfig = lastChanState.LocalChanCfg + var b bytes.Buffer if err := serializeChannelCloseSummary(&b, summary); err != nil { return err @@ -1834,11 +1866,37 @@ func putChannelCloseSummary(tx *bolt.Tx, chanID []byte, } func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error { - return writeElements(w, + err := writeElements(w, cs.ChanPoint, cs.ShortChanID, cs.ChainHash, cs.ClosingTXID, cs.CloseHeight, cs.RemotePub, cs.Capacity, cs.SettledBalance, cs.TimeLockedBalance, cs.CloseType, cs.IsPending, ) + if err != nil { + return err + } + + // If this is a close channel summary created before the addition of + // the new fields, then we can exit here. + if cs.RemoteCurrentRevocation == nil { + return nil + } + + if err := writeElements(w, cs.RemoteCurrentRevocation); err != nil { + return err + } + + if err := writeChanConfig(w, &cs.LocalChanConfig); err != nil { + return err + } + + // We'll write this field last, as it's possible for a channel to be + // closed before we learn of the next unrevoked revocation point for + // the remote party. + if cs.RemoteNextRevocation == nil { + return nil + } + + return writeElements(w, cs.RemoteNextRevocation) } func fetchChannelCloseSummary(tx *bolt.Tx, @@ -1870,9 +1928,49 @@ func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) { return nil, err } + // We'll now check to see if the channel close summary was encoded with + // any of the additional optional fields. + err = readElements(r, &c.RemoteCurrentRevocation) + switch { + case err == io.EOF: + return c, nil + + // If we got a non-eof error, then we know there's an actually issue. + // Otherwise, it may have been the case that this summary didn't have + // the set of optional fields. + case err != nil: + return nil, err + } + + if err := readChanConfig(r, &c.LocalChanConfig); err != nil { + return nil, err + } + + // Finally, we'll attempt to read the next unrevoked commitment point + // for the remote party. If we closed the channel before receiving a + // funding locked message, then this can be nil. As a result, we'll use + // the same technique to read the field, only if there's still data + // left in the buffer. + err = readElements(r, &c.RemoteNextRevocation) + if err != nil && err != io.EOF { + // If we got a non-eof error, then we know there's an actually + // issue. Otherwise, it may have been the case that this + // summary didn't have the set of optional fields. + return nil, err + } + return c, nil } +func writeChanConfig(b io.Writer, c *ChannelConfig) error { + return writeElements(b, + c.DustLimit, c.MaxPendingAmount, c.ChanReserve, c.MinHTLC, + c.MaxAcceptedHtlcs, c.CsvDelay, c.MultiSigKey, + c.RevocationBasePoint, c.PaymentBasePoint, c.DelayBasePoint, + c.HtlcBasePoint, + ) +} + func putChanInfo(chanBucket *bolt.Bucket, channel *OpenChannel) error { var w bytes.Buffer if err := writeElements(&w, @@ -1893,14 +1991,6 @@ func putChanInfo(chanBucket *bolt.Bucket, channel *OpenChannel) error { } } - writeChanConfig := func(b io.Writer, c *ChannelConfig) error { - return writeElements(b, - c.DustLimit, c.MaxPendingAmount, c.ChanReserve, c.MinHTLC, - c.MaxAcceptedHtlcs, c.CsvDelay, c.MultiSigKey, - c.RevocationBasePoint, c.PaymentBasePoint, c.DelayBasePoint, - c.HtlcBasePoint, - ) - } if err := writeChanConfig(&w, &channel.LocalChanCfg); err != nil { return err } @@ -1976,6 +2066,16 @@ func putChanRevocationState(chanBucket *bolt.Bucket, channel *OpenChannel) error return chanBucket.Put(revocationStateKey, b.Bytes()) } +func readChanConfig(b io.Reader, c *ChannelConfig) error { + return readElements(b, + &c.DustLimit, &c.MaxPendingAmount, &c.ChanReserve, + &c.MinHTLC, &c.MaxAcceptedHtlcs, &c.CsvDelay, + &c.MultiSigKey, &c.RevocationBasePoint, + &c.PaymentBasePoint, &c.DelayBasePoint, + &c.HtlcBasePoint, + ) +} + func fetchChanInfo(chanBucket *bolt.Bucket, channel *OpenChannel) error { infoBytes := chanBucket.Get(chanInfoKey) if infoBytes == nil { @@ -2001,15 +2101,6 @@ func fetchChanInfo(chanBucket *bolt.Bucket, channel *OpenChannel) error { } } - readChanConfig := func(b io.Reader, c *ChannelConfig) error { - return readElements(b, - &c.DustLimit, &c.MaxPendingAmount, &c.ChanReserve, - &c.MinHTLC, &c.MaxAcceptedHtlcs, &c.CsvDelay, - &c.MultiSigKey, &c.RevocationBasePoint, - &c.PaymentBasePoint, &c.DelayBasePoint, - &c.HtlcBasePoint, - ) - } if err := readChanConfig(r, &channel.LocalChanCfg); err != nil { return err } diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go index cf35d5588..897f7b6fd 100644 --- a/contractcourt/chain_watcher.go +++ b/contractcourt/chain_watcher.go @@ -340,40 +340,71 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) { ) remoteStateNum := remoteCommit.CommitHeight + remoteChainTip, err := c.cfg.chanState.RemoteCommitChainTip() + if err != nil && err != channeldb.ErrNoPendingCommit { + log.Errorf("unable to obtain chain tip for "+ + "ChannelPoint(%v): %v", + c.cfg.chanState.FundingOutpoint, err) + return + } + switch { // If state number spending transaction matches the // current latest state, then they've initiated a // unilateral close. So we'll trigger the unilateral // close signal so subscribers can clean up the state // as necessary. - // + case broadcastStateNum == remoteStateNum: + err := c.dispatchRemoteForceClose( + commitSpend, *remoteCommit, false, + ) + if err != nil { + log.Errorf("unable to handle remote "+ + "close for chan_point=%v: %v", + c.cfg.chanState.FundingOutpoint, err) + } + // We'll also handle the case of the remote party // broadcasting their commitment transaction which is // one height above ours. This case can arise when we // initiate a state transition, but the remote party // has a fail crash _after_ accepting the new state, // but _before_ sending their signature to us. - case broadcastStateNum >= remoteStateNum: - if err := c.dispatchRemoteForceClose( - commitSpend, *remoteCommit, - ); err != nil { + case broadcastStateNum == remoteStateNum+1 && + remoteChainTip != nil: + + err := c.dispatchRemoteForceClose( + commitSpend, remoteChainTip.Commitment, + true, + ) + if err != nil { log.Errorf("unable to handle remote "+ "close for chan_point=%v: %v", c.cfg.chanState.FundingOutpoint, err) } + // This is the case that somehow the commitment + // broadcast is actually greater than even one beyond + // our best known state number. This should NEVER + // happen, but we'll log it in any case. + case broadcastStateNum > remoteStateNum+1: + log.Errorf("Remote node broadcast state #%v, "+ + "which is more than 1 beyond best known "+ + "state #%v!!!", broadcastStateNum, + remoteStateNum) + // If the state number broadcast is lower than the // remote node's current un-revoked height, then // THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT // WITHIN THE PAYMENT CHANNEL. Therefore we close the // signal indicating a revoked broadcast to allow - // subscribers to - // swiftly dispatch justice!!! + // subscribers to swiftly dispatch justice!!! case broadcastStateNum < remoteStateNum: - if err := c.dispatchContractBreach( + err := c.dispatchContractBreach( commitSpend, remoteCommit, broadcastStateNum, - ); err != nil { + ) + if err != nil { log.Errorf("unable to handle channel "+ "breach for chan_point=%v: %v", c.cfg.chanState.FundingOutpoint, err) @@ -570,13 +601,16 @@ func (c *chainWatcher) dispatchLocalForceClose( return nil } -// dispatchRemoteForceClose processes a detected unilateral channel closure by the -// remote party. This function will prepare a UnilateralCloseSummary which will -// then be sent to any subscribers allowing them to resolve all our funds in -// the channel on chain. Once this close summary is prepared, all registered -// subscribers will receive a notification of this event. +// dispatchRemoteForceClose processes a detected unilateral channel closure by +// the remote party. This function will prepare a UnilateralCloseSummary which +// will then be sent to any subscribers allowing them to resolve all our funds +// in the channel on chain. Once this close summary is prepared, all registered +// subscribers will receive a notification of this event. The +// isRemotePendingCommit argument should be set to true if the remote node +// broadcast their pending commitment (w/o revoking their current settled +// commitment). func (c *chainWatcher) dispatchRemoteForceClose(commitSpend *chainntnfs.SpendDetail, - remoteCommit channeldb.ChannelCommitment) error { + remoteCommit channeldb.ChannelCommitment, isRemotePendingCommit bool) error { log.Infof("Unilateral close of ChannelPoint(%v) "+ "detected", c.cfg.chanState.FundingOutpoint) @@ -584,8 +618,9 @@ func (c *chainWatcher) dispatchRemoteForceClose(commitSpend *chainntnfs.SpendDet // First, we'll create a closure summary that contains all the // materials required to let each subscriber sweep the funds in the // channel on-chain. - uniClose, err := lnwallet.NewUnilateralCloseSummary(c.cfg.chanState, - c.cfg.signer, c.cfg.pCache, commitSpend, remoteCommit, + uniClose, err := lnwallet.NewUnilateralCloseSummary( + c.cfg.chanState, c.cfg.signer, c.cfg.pCache, commitSpend, + remoteCommit, isRemotePendingCommit, ) if err != nil { return err diff --git a/contractcourt/chain_watcher_test.go b/contractcourt/chain_watcher_test.go new file mode 100644 index 000000000..d5d4a4879 --- /dev/null +++ b/contractcourt/chain_watcher_test.go @@ -0,0 +1,207 @@ +package contractcourt + +import ( + "bytes" + "crypto/sha256" + "testing" + "time" + + "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/roasbeef/btcd/chaincfg/chainhash" + "github.com/roasbeef/btcd/wire" +) + +type mockNotifier struct { + spendChan chan *chainntnfs.SpendDetail +} + +func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs, + heightHint uint32) (*chainntnfs.ConfirmationEvent, error) { + return nil, nil +} +func (m *mockNotifier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent, error) { + return &chainntnfs.BlockEpochEvent{ + Epochs: make(chan *chainntnfs.BlockEpoch), + Cancel: func() {}, + }, nil +} + +func (m *mockNotifier) Start() error { + return nil +} + +func (m *mockNotifier) Stop() error { + return nil +} +func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, + heightHint uint32, _ bool) (*chainntnfs.SpendEvent, error) { + return &chainntnfs.SpendEvent{ + Spend: m.spendChan, + Cancel: func() {}, + }, nil +} + +// TestChainWatcherRemoteUnilateralClose tests that the chain watcher is able +// to properly detect a normal unilateral close by the remote node using their +// lowest commitment. +func TestChainWatcherRemoteUnilateralClose(t *testing.T) { + t.Parallel() + + // First, we'll create two channels which already have established a + // commitment contract between themselves. + aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels() + if err != nil { + t.Fatalf("unable to create test channels: %v", err) + } + defer cleanUp() + + // With the channels created, we'll now create a chain watcher instance + // which will be watching for any closes of Alice's channel. + aliceNotifier := &mockNotifier{ + spendChan: make(chan *chainntnfs.SpendDetail), + } + aliceChainWatcher, err := newChainWatcher(chainWatcherConfig{ + chanState: aliceChannel.State(), + notifier: aliceNotifier, + signer: aliceChannel.Signer, + }) + if err != nil { + t.Fatalf("unable to create chain watcher: %v", err) + } + err = aliceChainWatcher.Start() + if err != nil { + t.Fatalf("unable to start chain watcher: %v", err) + } + defer aliceChainWatcher.Stop() + + // We'll request a new channel event subscription from Alice's chain + // watcher. + chanEvents := aliceChainWatcher.SubscribeChannelEvents() + + // If we simulate an immediate broadcast of the current commitment by + // Bob, then the chain watcher should detect this case. + bobCommit := bobChannel.State().LocalCommitment.CommitTx + bobTxHash := bobCommit.TxHash() + bobSpend := &chainntnfs.SpendDetail{ + SpenderTxHash: &bobTxHash, + SpendingTx: bobCommit, + } + aliceNotifier.spendChan <- bobSpend + + // We should get a new spend event over the remote unilateral close + // event channel. + var uniClose *lnwallet.UnilateralCloseSummary + select { + case uniClose = <-chanEvents.RemoteUnilateralClosure: + case <-time.After(time.Second * 15): + t.Fatalf("didn't receive unilateral close event") + } + + // The unilateral close should have properly located Alice's output in + // the commitment transaction. + if uniClose.CommitResolution == nil { + t.Fatalf("unable to find alice's commit resolution") + } +} + +// TestChainWatcherRemoteUnilateralClosePendingCommit tests that the chain +// watcher is able to properly detect a unilateral close wherein the remote +// node broadcasts their newly received commitment, without first revoking the +// old one. +func TestChainWatcherRemoteUnilateralClosePendingCommit(t *testing.T) { + t.Parallel() + + // First, we'll create two channels which already have established a + // commitment contract between themselves. + aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels() + if err != nil { + t.Fatalf("unable to create test channels: %v", err) + } + defer cleanUp() + + // With the channels created, we'll now create a chain watcher instance + // which will be watching for any closes of Alice's channel. + aliceNotifier := &mockNotifier{ + spendChan: make(chan *chainntnfs.SpendDetail), + } + aliceChainWatcher, err := newChainWatcher(chainWatcherConfig{ + chanState: aliceChannel.State(), + notifier: aliceNotifier, + signer: aliceChannel.Signer, + }) + if err != nil { + t.Fatalf("unable to create chain watcher: %v", err) + } + if err := aliceChainWatcher.Start(); err != nil { + t.Fatalf("unable to start chain watcher: %v", err) + } + defer aliceChainWatcher.Stop() + + // We'll request a new channel event subscription from Alice's chain + // watcher. + chanEvents := aliceChainWatcher.SubscribeChannelEvents() + + // Next, we'll create a fake HTLC just so we can advance Alice's + // channel state to a new pending commitment on her remote commit chain + // for Bob. + htlcAmount := lnwire.NewMSatFromSatoshis(20000) + preimage := bytes.Repeat([]byte{byte(1)}, 32) + paymentHash := sha256.Sum256(preimage) + var returnPreimage [32]byte + copy(returnPreimage[:], preimage) + htlc := &lnwire.UpdateAddHTLC{ + ID: uint64(0), + PaymentHash: paymentHash, + Amount: htlcAmount, + Expiry: uint32(5), + } + + if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil { + t.Fatalf("alice unable to add htlc: %v", err) + } + if _, err := bobChannel.ReceiveHTLC(htlc); err != nil { + t.Fatalf("bob unable to recv add htlc: %v", err) + } + + // With the HTLC added, we'll now manually initiate a state transition + // from Alice to Bob. + _, _, err = aliceChannel.SignNextCommitment() + if err != nil { + t.Fatal(err) + } + + // At this point, we'll now Bob broadcasting this new pending unrevoked + // commitment. + bobPendingCommit, err := aliceChannel.State().RemoteCommitChainTip() + if err != nil { + t.Fatal(err) + } + + // We'll craft a fake spend notification with Bob's actual commitment. + // The chain watcher should be able to detect that this is a pending + // commit broadcast based on the state hints in the commitment. + bobCommit := bobPendingCommit.Commitment.CommitTx + bobTxHash := bobCommit.TxHash() + bobSpend := &chainntnfs.SpendDetail{ + SpenderTxHash: &bobTxHash, + SpendingTx: bobCommit, + } + aliceNotifier.spendChan <- bobSpend + + // We should get a new spend event over the remote unilateral close + // event channel. + var uniClose *lnwallet.UnilateralCloseSummary + select { + case uniClose = <-chanEvents.RemoteUnilateralClosure: + case <-time.After(time.Second * 15): + t.Fatalf("didn't receive unilateral close event") + } + + // The unilateral close should have properly located Alice's output in + // the commitment transaction. + if uniClose.CommitResolution == nil { + t.Fatalf("unable to find alice's commit resolution") + } +} diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 34039db0f..2cf85c1e6 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -1234,10 +1234,10 @@ func compactLogs(ourLog, theirLog *updateLog, // // See the individual comments within the above methods for further details. type LightningChannel struct { - // signer is the main signer instances that will be responsible for + // Signer is the main signer instances that will be responsible for // signing any HTLC and commitment transaction generated by the state // machine. - signer Signer + Signer Signer // signDesc is the primary sign descriptor that is capable of signing // the commitment transaction that spends the multi-sig output. @@ -1350,7 +1350,7 @@ func NewLightningChannel(signer Signer, pCache PreimageCache, lc := &LightningChannel{ // TODO(roasbeef): tune num sig workers? sigPool: newSigPool(runtime.NumCPU(), signer), - signer: signer, + Signer: signer, pCache: pCache, currentHeight: localCommit.CommitHeight, remoteCommitChain: newCommitmentChain(remoteCommit.CommitHeight), @@ -2920,7 +2920,7 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, erro // the new commitment transaction while we're waiting for the rest of // the HTLC signatures to be processed. lc.signDesc.SigHashes = txscript.NewTxSigHashes(newCommitView.txn) - rawSig, err := lc.signer.SignOutputRaw(newCommitView.txn, lc.signDesc) + rawSig, err := lc.Signer.SignOutputRaw(newCommitView.txn, lc.signDesc) if err != nil { close(cancelChan) return sig, htlcSigs, err @@ -4601,7 +4601,7 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) { // With this, we then generate the full witness so the caller can // broadcast a fully signed transaction. lc.signDesc.SigHashes = txscript.NewTxSigHashes(commitTx) - ourSigRaw, err := lc.signer.SignOutputRaw(commitTx, lc.signDesc) + ourSigRaw, err := lc.Signer.SignOutputRaw(commitTx, lc.signDesc) if err != nil { return nil, err } @@ -4678,14 +4678,24 @@ type UnilateralCloseSummary struct { // NewUnilateralCloseSummary creates a new summary that provides the caller // with all the information required to claim all funds on chain in the event -// that the remote party broadcasts their commitment. +// that the remote party broadcasts their commitment. If the +// remotePendingCommit value is set to true, then we'll use the next (second) +// unrevoked commitment point to construct the summary. Otherwise, we assume +// that the remote party broadcast the lower of their two possible commits. func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer Signer, pCache PreimageCache, commitSpend *chainntnfs.SpendDetail, - remoteCommit channeldb.ChannelCommitment) (*UnilateralCloseSummary, error) { + remoteCommit channeldb.ChannelCommitment, + remotePendingCommit bool) (*UnilateralCloseSummary, error) { // First, we'll generate the commitment point and the revocation point - // so we can re-construct the HTLC state and also our payment key. + // so we can re-construct the HTLC state and also our payment key. If + // this is the pending remote commitment, then we'll use the second + // unrevoked commit point in order to properly reconstruct the scripts + // we need to locate. commitPoint := chanState.RemoteCurrentRevocation + if remotePendingCommit { + commitPoint = chanState.RemoteNextRevocation + } keyRing := deriveCommitmentKeys( commitPoint, false, &chanState.LocalChanCfg, &chanState.RemoteChanCfg, @@ -5258,7 +5268,7 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) { localCommitment := lc.channelState.LocalCommitment summary, err := NewLocalForceCloseSummary(lc.channelState, - lc.signer, lc.pCache, commitTx, localCommitment) + lc.Signer, lc.pCache, commitTx, localCommitment) if err != nil { return nil, err } @@ -5427,7 +5437,7 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount, // using the generated txid to be notified once the closure transaction // has been confirmed. lc.signDesc.SigHashes = txscript.NewTxSigHashes(closeTx) - sig, err := lc.signer.SignOutputRaw(closeTx, lc.signDesc) + sig, err := lc.Signer.SignOutputRaw(closeTx, lc.signDesc) if err != nil { return nil, nil, 0, err } diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index b36c393c7..69d10d1cc 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -2,122 +2,21 @@ package lnwallet import ( "bytes" - "crypto/rand" "crypto/sha256" - "encoding/binary" - "io" - "io/ioutil" - "os" "reflect" "runtime" "testing" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" - "github.com/lightningnetwork/lnd/channeldb" - "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/shachain" "github.com/roasbeef/btcd/blockchain" - "github.com/roasbeef/btcd/btcec" - "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) -var ( - privPass = []byte("private-test") - - // For simplicity a single priv key controls all of our test outputs. - testWalletPrivKey = []byte{ - 0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf, - 0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9, - 0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f, - 0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90, - } - - // We're alice :) - bobsPrivKey = []byte{ - 0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, - 0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, - 0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, - 0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9, - } - - // Use a hard-coded HD seed. - testHdSeed = chainhash.Hash{ - 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, - 0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4, - 0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9, - 0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, - } - - // The number of confirmations required to consider any created channel - // open. - numReqConfs = uint16(1) - - // A serializable txn for testing funding txn. - testTx = &wire.MsgTx{ - Version: 1, - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: wire.OutPoint{ - Hash: chainhash.Hash{}, - Index: 0xffffffff, - }, - SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62}, - Sequence: 0xffffffff, - }, - }, - TxOut: []*wire.TxOut{ - { - Value: 5000000000, - PkScript: []byte{ - 0x41, // OP_DATA_65 - 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, - 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, - 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, - 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, - 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, - 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, - 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, - 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, - 0xa6, // 65-byte signature - 0xac, // OP_CHECKSIG - }, - }, - }, - LockTime: 5, - } -) - -// initRevocationWindows simulates a new channel being opened within the p2p -// network by populating the initial revocation windows of the passed -// commitment state machines. -// -// TODO(roasbeef): rename! -func initRevocationWindows(chanA, chanB *LightningChannel, windowSize int) error { - aliceNextRevoke, err := chanA.NextRevocationKey() - if err != nil { - return err - } - if err := chanB.InitNextRevocation(aliceNextRevoke); err != nil { - return err - } - - bobNextRevoke, err := chanB.NextRevocationKey() - if err != nil { - return err - } - if err := chanA.InitNextRevocation(bobNextRevoke); err != nil { - return err - } - - return nil -} - // forceStateTransition executes the necessary interaction between the two // commitment state machines to transition to a new state locking in any // pending updates. @@ -157,277 +56,6 @@ func forceStateTransition(chanA, chanB *LightningChannel) error { return nil } -// createTestChannels creates two test lightning channels using the provided -// notifier. The channel itself is funded with 10 BTC, with 5 BTC allocated to -// each side. Within the channel, Alice is the initiator. -func createTestChannels(revocationWindow int) (*LightningChannel, - *LightningChannel, func(), error) { - - channelCapacity, err := btcutil.NewAmount(10) - if err != nil { - return nil, nil, nil, err - } - - channelBal := channelCapacity / 2 - aliceDustLimit := btcutil.Amount(200) - bobDustLimit := btcutil.Amount(1300) - csvTimeoutAlice := uint32(5) - csvTimeoutBob := uint32(4) - - prevOut := &wire.OutPoint{ - Hash: chainhash.Hash(testHdSeed), - Index: 0, - } - fundingTxIn := wire.NewTxIn(prevOut, nil, nil) - - // For each party, we'll create a distinct set of keys in order to - // emulate the typical set up with live channels. - var ( - aliceKeys []*btcec.PrivateKey - bobKeys []*btcec.PrivateKey - ) - for i := 0; i < 5; i++ { - key := make([]byte, len(testWalletPrivKey)) - copy(key[:], testWalletPrivKey[:]) - key[0] ^= byte(i + 1) - - aliceKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), key) - aliceKeys = append(aliceKeys, aliceKey) - - key = make([]byte, len(bobsPrivKey)) - copy(key[:], bobsPrivKey) - key[0] ^= byte(i + 1) - - bobKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), key) - bobKeys = append(bobKeys, bobKey) - } - - aliceCfg := channeldb.ChannelConfig{ - ChannelConstraints: channeldb.ChannelConstraints{ - DustLimit: aliceDustLimit, - MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity), - ChanReserve: channelCapacity / 100, - MinHTLC: 0, - MaxAcceptedHtlcs: MaxHTLCNumber / 2, - }, - CsvDelay: uint16(csvTimeoutAlice), - MultiSigKey: keychain.KeyDescriptor{ - PubKey: aliceKeys[0].PubKey(), - }, - RevocationBasePoint: keychain.KeyDescriptor{ - PubKey: aliceKeys[1].PubKey(), - }, - PaymentBasePoint: keychain.KeyDescriptor{ - PubKey: aliceKeys[2].PubKey(), - }, - DelayBasePoint: keychain.KeyDescriptor{ - PubKey: aliceKeys[3].PubKey(), - }, - HtlcBasePoint: keychain.KeyDescriptor{ - PubKey: aliceKeys[4].PubKey(), - }, - } - bobCfg := channeldb.ChannelConfig{ - ChannelConstraints: channeldb.ChannelConstraints{ - DustLimit: bobDustLimit, - MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity), - ChanReserve: channelCapacity / 100, - MinHTLC: 0, - MaxAcceptedHtlcs: MaxHTLCNumber / 2, - }, - CsvDelay: uint16(csvTimeoutBob), - MultiSigKey: keychain.KeyDescriptor{ - PubKey: bobKeys[0].PubKey(), - }, - RevocationBasePoint: keychain.KeyDescriptor{ - PubKey: bobKeys[1].PubKey(), - }, - PaymentBasePoint: keychain.KeyDescriptor{ - PubKey: bobKeys[2].PubKey(), - }, - DelayBasePoint: keychain.KeyDescriptor{ - PubKey: bobKeys[3].PubKey(), - }, - HtlcBasePoint: keychain.KeyDescriptor{ - PubKey: bobKeys[4].PubKey(), - }, - } - - bobRoot, err := chainhash.NewHash(bobKeys[0].Serialize()) - if err != nil { - return nil, nil, nil, err - } - bobPreimageProducer := shachain.NewRevocationProducer(*bobRoot) - bobFirstRevoke, err := bobPreimageProducer.AtIndex(0) - if err != nil { - return nil, nil, nil, err - } - bobCommitPoint := ComputeCommitmentPoint(bobFirstRevoke[:]) - - aliceRoot, err := chainhash.NewHash(aliceKeys[0].Serialize()) - if err != nil { - return nil, nil, nil, err - } - alicePreimageProducer := shachain.NewRevocationProducer(*aliceRoot) - aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0) - if err != nil { - return nil, nil, nil, err - } - aliceCommitPoint := ComputeCommitmentPoint(aliceFirstRevoke[:]) - - aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns(channelBal, - channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint, - *fundingTxIn) - if err != nil { - return nil, nil, nil, err - } - - alicePath, err := ioutil.TempDir("", "alicedb") - dbAlice, err := channeldb.Open(alicePath) - if err != nil { - return nil, nil, nil, err - } - - bobPath, err := ioutil.TempDir("", "bobdb") - dbBob, err := channeldb.Open(bobPath) - if err != nil { - return nil, nil, nil, err - } - - estimator := &StaticFeeEstimator{24} - feePerVSize, err := estimator.EstimateFeePerVSize(1) - if err != nil { - return nil, nil, nil, err - } - feePerKw := feePerVSize.FeePerKWeight() - commitFee := calcStaticFee(0) - - aliceCommit := channeldb.ChannelCommitment{ - CommitHeight: 0, - LocalBalance: lnwire.NewMSatFromSatoshis(channelBal - commitFee), - RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal), - CommitFee: commitFee, - FeePerKw: btcutil.Amount(feePerKw), - CommitTx: aliceCommitTx, - CommitSig: bytes.Repeat([]byte{1}, 71), - } - bobCommit := channeldb.ChannelCommitment{ - CommitHeight: 0, - LocalBalance: lnwire.NewMSatFromSatoshis(channelBal), - RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal - commitFee), - CommitFee: commitFee, - FeePerKw: btcutil.Amount(feePerKw), - CommitTx: bobCommitTx, - CommitSig: bytes.Repeat([]byte{1}, 71), - } - - var chanIDBytes [8]byte - if _, err := io.ReadFull(rand.Reader, chanIDBytes[:]); err != nil { - return nil, nil, nil, err - } - - shortChanID := lnwire.NewShortChanIDFromInt( - binary.BigEndian.Uint64(chanIDBytes[:]), - ) - - aliceChannelState := &channeldb.OpenChannel{ - LocalChanCfg: aliceCfg, - RemoteChanCfg: bobCfg, - IdentityPub: aliceKeys[0].PubKey(), - FundingOutpoint: *prevOut, - ShortChanID: shortChanID, - ChanType: channeldb.SingleFunder, - IsInitiator: true, - Capacity: channelCapacity, - RemoteCurrentRevocation: bobCommitPoint, - RevocationProducer: alicePreimageProducer, - RevocationStore: shachain.NewRevocationStore(), - LocalCommitment: aliceCommit, - RemoteCommitment: aliceCommit, - Db: dbAlice, - Packager: channeldb.NewChannelPackager(shortChanID), - FundingTxn: testTx, - } - bobChannelState := &channeldb.OpenChannel{ - LocalChanCfg: bobCfg, - RemoteChanCfg: aliceCfg, - IdentityPub: bobKeys[0].PubKey(), - FundingOutpoint: *prevOut, - ShortChanID: shortChanID, - ChanType: channeldb.SingleFunder, - IsInitiator: false, - Capacity: channelCapacity, - RemoteCurrentRevocation: aliceCommitPoint, - RevocationProducer: bobPreimageProducer, - RevocationStore: shachain.NewRevocationStore(), - LocalCommitment: bobCommit, - RemoteCommitment: bobCommit, - Db: dbBob, - Packager: channeldb.NewChannelPackager(shortChanID), - } - - aliceSigner := &mockSigner{privkeys: aliceKeys} - bobSigner := &mockSigner{privkeys: bobKeys} - - pCache := &mockPreimageCache{ - // hash -> preimage - preimageMap: make(map[[32]byte][]byte), - } - - // TODO(roasbeef): make mock version of pre-image store - channelAlice, err := NewLightningChannel( - aliceSigner, pCache, aliceChannelState, - ) - if err != nil { - return nil, nil, nil, err - } - channelBob, err := NewLightningChannel( - bobSigner, pCache, bobChannelState, - ) - if err != nil { - return nil, nil, nil, err - } - - if err := channelAlice.channelState.FullSync(); err != nil { - return nil, nil, nil, err - } - if err := channelBob.channelState.FullSync(); err != nil { - return nil, nil, nil, err - } - - cleanUpFunc := func() { - os.RemoveAll(bobPath) - os.RemoveAll(alicePath) - - channelAlice.Stop() - channelBob.Stop() - } - - // Now that the channel are open, simulate the start of a session by - // having Alice and Bob extend their revocation windows to each other. - err = initRevocationWindows(channelAlice, channelBob, revocationWindow) - if err != nil { - return nil, nil, nil, err - } - - return channelAlice, channelBob, cleanUpFunc, nil -} - -// calcStaticFee calculates appropriate fees for commitment transactions. This -// function provides a simple way to allow test balance assertions to take fee -// calculations into account. -// -// TODO(bvu): Refactor when dynamic fee estimation is added. -func calcStaticFee(numHTLCs int) btcutil.Amount { - const ( - commitWeight = btcutil.Amount(724) - htlcWeight = 172 - feePerKw = btcutil.Amount(24/4) * 1000 - ) - return feePerKw * (commitWeight + - btcutil.Amount(htlcWeight*numHTLCs)) / 1000 -} - // createHTLC is a utility function for generating an HTLC with a given // preimage and a given amount. func createHTLC(id int, amount lnwire.MilliSatoshi) (*lnwire.UpdateAddHTLC, [32]byte) { @@ -473,7 +101,7 @@ func TestSimpleAddSettleWorkflow(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -783,7 +411,7 @@ func TestCheckCommitTxSize(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -842,7 +470,7 @@ func TestCooperativeChannelClosure(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -909,7 +537,7 @@ func TestForceClose(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(3) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -1048,7 +676,7 @@ func TestForceClose(t *testing.T) { Value: htlcResolution.SweepSignDesc.Output.Value, }) htlcResolution.SweepSignDesc.InputIndex = 0 - sweepTx.TxIn[0].Witness, err = htlcSpendSuccess(aliceChannel.signer, + sweepTx.TxIn[0].Witness, err = htlcSpendSuccess(aliceChannel.Signer, &htlcResolution.SweepSignDesc, sweepTx, uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay)) if err != nil { @@ -1109,7 +737,7 @@ func TestForceClose(t *testing.T) { Value: inHtlcResolution.SweepSignDesc.Output.Value, }) inHtlcResolution.SweepSignDesc.InputIndex = 0 - sweepTx.TxIn[0].Witness, err = htlcSpendSuccess(aliceChannel.signer, + sweepTx.TxIn[0].Witness, err = htlcSpendSuccess(aliceChannel.Signer, &inHtlcResolution.SweepSignDesc, sweepTx, uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay)) if err != nil { @@ -1198,7 +826,7 @@ func TestForceCloseDustOutput(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(3) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -1316,7 +944,7 @@ func TestDustHTLCFees(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(3) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -1393,7 +1021,7 @@ func TestHTLCDustLimit(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(3) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -1478,7 +1106,7 @@ func TestHTLCSigNumber(t *testing.T) { // Create a test channel funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. Alice's dustlimit is 200 sat, while // Bob has 1300 sat. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(3) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -1649,7 +1277,7 @@ func TestChannelBalanceDustLimit(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(3) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -1717,7 +1345,7 @@ func TestStateUpdatePersistence(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -1815,13 +1443,13 @@ func TestStateUpdatePersistence(t *testing.T) { t.Fatalf("unable to fetch channel: %v", err) } aliceChannelNew, err := NewLightningChannel( - aliceChannel.signer, nil, aliceChannels[0], + aliceChannel.Signer, nil, aliceChannels[0], ) if err != nil { t.Fatalf("unable to create new channel: %v", err) } bobChannelNew, err := NewLightningChannel( - bobChannel.signer, nil, bobChannels[0], + bobChannel.Signer, nil, bobChannels[0], ) if err != nil { t.Fatalf("unable to create new channel: %v", err) @@ -2008,7 +1636,7 @@ func TestCancelHTLC(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(5) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -2122,7 +1750,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(5) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -2283,7 +1911,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) { func TestUpdateFeeAdjustments(t *testing.T) { t.Parallel() - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -2338,7 +1966,7 @@ func TestUpdateFeeAdjustments(t *testing.T) { func TestUpdateFeeFail(t *testing.T) { t.Parallel() - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -2374,7 +2002,7 @@ func TestUpdateFeeSenderCommits(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -2486,7 +2114,7 @@ func TestUpdateFeeReceiverCommits(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -2624,7 +2252,7 @@ func TestUpdateFeeReceiverSendsUpdate(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -2653,7 +2281,7 @@ func TestUpdateFeeMultipleUpdates(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -2763,7 +2391,7 @@ func TestAddHTLCNegativeBalance(t *testing.T) { // We'll kick off the test by creating our channels which both are // loaded with 5 BTC each. - aliceChannel, _, cleanUp, err := createTestChannels(1) + aliceChannel, _, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -2844,7 +2472,7 @@ func TestChanSyncFullySynced(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -2915,14 +2543,14 @@ func TestChanSyncFullySynced(t *testing.T) { t.Fatalf("unable to fetch channel: %v", err) } aliceChannelNew, err := NewLightningChannel( - aliceChannel.signer, nil, aliceChannels[0], + aliceChannel.Signer, nil, aliceChannels[0], ) if err != nil { t.Fatalf("unable to create new channel: %v", err) } defer aliceChannelNew.Stop() bobChannelNew, err := NewLightningChannel( - bobChannel.signer, nil, bobChannels[0], + bobChannel.Signer, nil, bobChannels[0], ) if err != nil { t.Fatalf("unable to create new channel: %v", err) @@ -2945,7 +2573,7 @@ func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) { } channelNew, err := NewLightningChannel( - channelOld.signer, channelOld.pCache, nodeChannels[0], + channelOld.Signer, channelOld.pCache, nodeChannels[0], ) if err != nil { return nil, err @@ -2964,7 +2592,7 @@ func TestChanSyncOweCommitment(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -3278,7 +2906,7 @@ func TestChanSyncOweRevocation(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -3468,7 +3096,7 @@ func TestChanSyncOweRevocationAndCommit(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -3637,7 +3265,7 @@ func TestChanSyncOweRevocationAndCommitForceTransition(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -3821,7 +3449,7 @@ func TestFeeUpdateRejectInsaneFee(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, _, cleanUp, err := createTestChannels(1) + aliceChannel, _, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -3847,7 +3475,7 @@ func TestChannelRetransmissionFeeUpdate(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -4032,7 +3660,7 @@ func TestChanSyncUnableToSync(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -4069,7 +3697,7 @@ func TestChanSyncInvalidLastSecret(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -4159,7 +3787,7 @@ func TestChanAvailableBandwidth(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -4282,7 +3910,7 @@ func TestSignCommitmentFailNotLockedIn(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, _, cleanUp, err := createTestChannels(1) + aliceChannel, _, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -4307,7 +3935,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) { t.Parallel() // First, we'll make a channel between Alice and Bob. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -4447,7 +4075,7 @@ func TestInvalidCommitSigError(t *testing.T) { t.Parallel() // First, we'll make a channel between Alice and Bob. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -4494,7 +4122,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { // Create a test channel which will be used for the duration of this // unittest. The channel will be funded evenly with Alice having 5 BTC, // and Bob having 5 BTC. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(3) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -4545,8 +4173,8 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { SpenderTxHash: &commitTxHash, } aliceCloseSummary, err := NewUnilateralCloseSummary( - aliceChannel.channelState, aliceChannel.signer, aliceChannel.pCache, - spendDetail, aliceChannel.channelState.RemoteCommitment, + aliceChannel.channelState, aliceChannel.Signer, aliceChannel.pCache, + spendDetail, aliceChannel.channelState.RemoteCommitment, false, ) if err != nil { t.Fatalf("unable to create alice close summary: %v", err) @@ -4588,7 +4216,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { // With the transaction constructed, we'll generate a witness that // should be valid for it, and verify using an instance of Script. sweepTx.TxIn[0].Witness, err = receiverHtlcSpendTimeout( - aliceChannel.signer, &outHtlcResolution.SweepSignDesc, + aliceChannel.Signer, &outHtlcResolution.SweepSignDesc, sweepTx, int32(outHtlcResolution.Expiry), ) if err != nil { @@ -4622,7 +4250,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { sweepTx, ) sweepTx.TxIn[0].Witness, err = SenderHtlcSpendRedeem( - aliceChannel.signer, &inHtlcResolution.SweepSignDesc, + aliceChannel.Signer, &inHtlcResolution.SweepSignDesc, sweepTx, preimageBob[:], ) if err != nil { @@ -4645,6 +4273,121 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) { } } +// TestChannelUnilateralClosePendingCommit tests that if the remote party +// broadcasts their pending commit (hasn't yet revoked the lower one), then +// we'll create a proper unilateral channel clsoure that can sweep the created +// outputs. +func TestChannelUnilateralClosePendingCommit(t *testing.T) { + t.Parallel() + + // Create a test channel which will be used for the duration of this + // unittest. The channel will be funded evenly with Alice having 5 BTC, + // and Bob having 5 BTC. + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() + if err != nil { + t.Fatalf("unable to create test channels: %v", err) + } + defer cleanUp() + + // First, we'll add an HTLC from Alice to Bob, just to be be able to + // create a new state transition. + htlcAmount := lnwire.NewMSatFromSatoshis(20000) + htlcAlice, _ := createHTLC(0, htlcAmount) + if _, err := aliceChannel.AddHTLC(htlcAlice, nil); err != nil { + t.Fatalf("alice unable to add htlc: %v", err) + } + if _, err := bobChannel.ReceiveHTLC(htlcAlice); err != nil { + t.Fatalf("bob unable to recv add htlc: %v", err) + } + + // With the HTLC added, we'll now manually initiate a state transition + // from Alice to Bob. + _, _, err = aliceChannel.SignNextCommitment() + if err != nil { + t.Fatal(err) + } + + // At this point, Alice's commitment chain should have a new pending + // commit for Bob. We'll extract it so we can simulate Bob broadcasting + // the commitment due to an issue. + bobCommit := aliceChannel.remoteCommitChain.tip().txn + bobTxHash := bobCommit.TxHash() + spendDetail := &chainntnfs.SpendDetail{ + SpenderTxHash: &bobTxHash, + SpendingTx: bobCommit, + } + + // At this point, if we attempt to create a unilateral close summary + // using this commitment, but with the wrong state, we should find that + // our output wasn't picked up. + aliceWrongCloseSummary, err := NewUnilateralCloseSummary( + aliceChannel.channelState, aliceChannel.Signer, aliceChannel.pCache, + spendDetail, aliceChannel.channelState.RemoteCommitment, false, + ) + if err != nil { + t.Fatalf("unable to create alice close summary: %v", err) + } + + if aliceWrongCloseSummary.CommitResolution != nil { + t.Fatalf("alice shouldn't have found self output") + } + + // If we create the close summary again, but this time use Alice's + // pending commit to Bob, then the unilateral close summary should be + // properly populated. + aliceRemoteChainTip, err := aliceChannel.channelState.RemoteCommitChainTip() + if err != nil { + t.Fatalf("unable to fetch remote chain tip: %v", err) + } + aliceCloseSummary, err := NewUnilateralCloseSummary( + aliceChannel.channelState, aliceChannel.Signer, aliceChannel.pCache, + spendDetail, aliceRemoteChainTip.Commitment, true, + ) + if err != nil { + t.Fatalf("unable to create alice close summary: %v", err) + } + + // With this proper version, Alice's commit resolution should have been + // properly located. + if aliceCloseSummary.CommitResolution == nil { + t.Fatalf("unable to find alice's commit resolution") + } + + aliceSignDesc := aliceCloseSummary.CommitResolution.SelfOutputSignDesc + + // Finally, we'll ensure that we're able to properly sweep our output + // from using the materials within the unilateral close summary. + sweepTx := wire.NewMsgTx(2) + sweepTx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: aliceCloseSummary.CommitResolution.SelfOutPoint, + }) + sweepTx.AddTxOut(&wire.TxOut{ + PkScript: testHdSeed[:], + Value: aliceSignDesc.Output.Value, + }) + aliceSignDesc.SigHashes = txscript.NewTxSigHashes(sweepTx) + sweepTx.TxIn[0].Witness, err = CommitSpendNoDelay( + aliceChannel.Signer, &aliceSignDesc, sweepTx, + ) + if err != nil { + t.Fatalf("unable to generate sweep witness: %v", err) + } + + // If we validate the signature on the new sweep transaction, it should + // be fully valid. + vm, err := txscript.NewEngine( + aliceSignDesc.Output.PkScript, + sweepTx, 0, txscript.StandardVerifyFlags, nil, + nil, aliceSignDesc.Output.Value, + ) + if err != nil { + t.Fatalf("unable to create engine: %v", err) + } + if err := vm.Execute(); err != nil { + t.Fatalf("htlc timeout spend is invalid: %v", err) + } +} + // TestDesyncHTLCs checks that we cannot add HTLCs that would make the // balance negative, when the remote and local update logs are desynced. func TestDesyncHTLCs(t *testing.T) { @@ -4652,7 +4395,7 @@ func TestDesyncHTLCs(t *testing.T) { // We'll kick off the test by creating our channels which both are // loaded with 5 BTC each. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -4719,7 +4462,7 @@ func TestMaxAcceptedHTLCs(t *testing.T) { // We'll kick off the test by creating our channels which both are // loaded with 5 BTC each. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -4780,7 +4523,7 @@ func TestMaxPendingAmount(t *testing.T) { // We'll kick off the test by creating our channels which both are // loaded with 5 BTC each. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -4838,9 +4581,9 @@ func TestChanReserve(t *testing.T) { t.Parallel() setupChannels := func() (*LightningChannel, *LightningChannel, func()) { - // We'll kick off the test by creating our channels which both - // are loaded with 5 BTC each. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + // We'll kick off the test by creating our channels which both are + // loaded with 5 BTC each. + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -5022,7 +4765,7 @@ func TestMinHTLC(t *testing.T) { // We'll kick off the test by creating our channels which both are // loaded with 5 BTC each. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } @@ -5079,7 +4822,7 @@ func TestNewBreachRetributionSkipsDustHtlcs(t *testing.T) { // We'll kick off the test by creating our channels which both are // loaded with 5 BTC each. - aliceChannel, bobChannel, cleanUp, err := createTestChannels(1) + aliceChannel, bobChannel, cleanUp, err := CreateTestChannels() if err != nil { t.Fatalf("unable to create test channels: %v", err) } diff --git a/lnwallet/common_test.go b/lnwallet/common_test.go deleted file mode 100644 index 424d34daa..000000000 --- a/lnwallet/common_test.go +++ /dev/null @@ -1,207 +0,0 @@ -package lnwallet - -import ( - "bytes" - "crypto/sha256" - "encoding/hex" - "fmt" - "sync" - - "github.com/roasbeef/btcd/btcec" - "github.com/roasbeef/btcd/chaincfg" - "github.com/roasbeef/btcd/txscript" - "github.com/roasbeef/btcd/wire" - "github.com/roasbeef/btcutil" -) - -// mockSigner is a simple implementation of the Signer interface. Each one has -// a set of private keys in a slice and can sign messages using the appropriate -// one. -type mockSigner struct { - privkeys []*btcec.PrivateKey - netParams *chaincfg.Params -} - -func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error) { - pubkey := signDesc.KeyDesc.PubKey - switch { - case signDesc.SingleTweak != nil: - pubkey = TweakPubKeyWithTweak(pubkey, signDesc.SingleTweak) - case signDesc.DoubleTweak != nil: - pubkey = DeriveRevocationPubkey(pubkey, signDesc.DoubleTweak.PubKey()) - } - - hash160 := btcutil.Hash160(pubkey.SerializeCompressed()) - privKey := m.findKey(hash160, signDesc.SingleTweak, signDesc.DoubleTweak) - if privKey == nil { - return nil, fmt.Errorf("Mock signer does not have key") - } - - sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes, - signDesc.InputIndex, signDesc.Output.Value, signDesc.WitnessScript, - txscript.SigHashAll, privKey) - if err != nil { - return nil, err - } - - return sig[:len(sig)-1], nil -} - -func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) { - scriptType, addresses, _, err := txscript.ExtractPkScriptAddrs( - signDesc.Output.PkScript, m.netParams) - if err != nil { - return nil, err - } - - switch scriptType { - case txscript.PubKeyHashTy: - privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, - signDesc.DoubleTweak) - if privKey == nil { - return nil, fmt.Errorf("Mock signer does not have key for "+ - "address %v", addresses[0]) - } - - scriptSig, err := txscript.SignatureScript(tx, signDesc.InputIndex, - signDesc.Output.PkScript, txscript.SigHashAll, privKey, true) - if err != nil { - return nil, err - } - - return &InputScript{ScriptSig: scriptSig}, nil - - case txscript.WitnessV0PubKeyHashTy: - privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, - signDesc.DoubleTweak) - if privKey == nil { - return nil, fmt.Errorf("Mock signer does not have key for "+ - "address %v", addresses[0]) - } - - witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes, - signDesc.InputIndex, signDesc.Output.Value, - signDesc.Output.PkScript, txscript.SigHashAll, privKey, true) - if err != nil { - return nil, err - } - - return &InputScript{Witness: witnessScript}, nil - - default: - return nil, fmt.Errorf("Unexpected script type: %v", scriptType) - } -} - -// findKey searches through all stored private keys and returns one -// corresponding to the hashed pubkey if it can be found. The public key may -// either correspond directly to the private key or to the private key with a -// tweak applied. -func (m *mockSigner) findKey(needleHash160 []byte, singleTweak []byte, - doubleTweak *btcec.PrivateKey) *btcec.PrivateKey { - - for _, privkey := range m.privkeys { - // First check whether public key is directly derived from private key. - hash160 := btcutil.Hash160(privkey.PubKey().SerializeCompressed()) - if bytes.Equal(hash160, needleHash160) { - return privkey - } - - // Otherwise check if public key is derived from tweaked private key. - switch { - case singleTweak != nil: - privkey = TweakPrivKey(privkey, singleTweak) - case doubleTweak != nil: - privkey = DeriveRevocationPrivKey(privkey, doubleTweak) - default: - continue - } - hash160 = btcutil.Hash160(privkey.PubKey().SerializeCompressed()) - if bytes.Equal(hash160, needleHash160) { - return privkey - } - } - return nil -} - -type mockPreimageCache struct { - sync.Mutex - preimageMap map[[32]byte][]byte -} - -func (m *mockPreimageCache) LookupPreimage(hash []byte) ([]byte, bool) { - m.Lock() - defer m.Unlock() - - var h [32]byte - copy(h[:], hash) - - p, ok := m.preimageMap[h] - return p, ok -} - -func (m *mockPreimageCache) AddPreimage(preimage []byte) error { - m.Lock() - defer m.Unlock() - - m.preimageMap[sha256.Sum256(preimage[:])] = preimage - - return nil -} - -// pubkeyFromHex parses a Bitcoin public key from a hex encoded string. -func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) { - bytes, err := hex.DecodeString(keyHex) - if err != nil { - return nil, err - } - return btcec.ParsePubKey(bytes, btcec.S256()) -} - -// privkeyFromHex parses a Bitcoin private key from a hex encoded string. -func privkeyFromHex(keyHex string) (*btcec.PrivateKey, error) { - bytes, err := hex.DecodeString(keyHex) - if err != nil { - return nil, err - } - key, _ := btcec.PrivKeyFromBytes(btcec.S256(), bytes) - return key, nil - -} - -// pubkeyToHex serializes a Bitcoin public key to a hex encoded string. -func pubkeyToHex(key *btcec.PublicKey) string { - return hex.EncodeToString(key.SerializeCompressed()) -} - -// privkeyFromHex serializes a Bitcoin private key to a hex encoded string. -func privkeyToHex(key *btcec.PrivateKey) string { - return hex.EncodeToString(key.Serialize()) -} - -// signatureFromHex parses a Bitcoin signature from a hex encoded string. -func signatureFromHex(sigHex string) (*btcec.Signature, error) { - bytes, err := hex.DecodeString(sigHex) - if err != nil { - return nil, err - } - return btcec.ParseSignature(bytes, btcec.S256()) -} - -// blockFromHex parses a full Bitcoin block from a hex encoded string. -func blockFromHex(blockHex string) (*btcutil.Block, error) { - bytes, err := hex.DecodeString(blockHex) - if err != nil { - return nil, err - } - return btcutil.NewBlockFromBytes(bytes) -} - -// txFromHex parses a full Bitcoin transaction from a hex encoded string. -func txFromHex(txHex string) (*btcutil.Tx, error) { - bytes, err := hex.DecodeString(txHex) - if err != nil { - return nil, err - } - return btcutil.NewTxFromBytes(bytes) -} diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go new file mode 100644 index 000000000..27ee491f3 --- /dev/null +++ b/lnwallet/test_utils.go @@ -0,0 +1,592 @@ +package lnwallet + +import ( + "bytes" + "crypto/rand" + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "fmt" + "io" + "io/ioutil" + "os" + "sync" + + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/shachain" + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/chaincfg" + "github.com/roasbeef/btcd/chaincfg/chainhash" + "github.com/roasbeef/btcd/txscript" + "github.com/roasbeef/btcd/wire" + "github.com/roasbeef/btcutil" +) + +var ( + privPass = []byte("private-test") + + // For simplicity a single priv key controls all of our test outputs. + testWalletPrivKey = []byte{ + 0x2b, 0xd8, 0x06, 0xc9, 0x7f, 0x0e, 0x00, 0xaf, + 0x1a, 0x1f, 0xc3, 0x32, 0x8f, 0xa7, 0x63, 0xa9, + 0x26, 0x97, 0x23, 0xc8, 0xdb, 0x8f, 0xac, 0x4f, + 0x93, 0xaf, 0x71, 0xdb, 0x18, 0x6d, 0x6e, 0x90, + } + + // We're alice :) + bobsPrivKey = []byte{ + 0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, + 0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, + 0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, + 0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9, + } + + // Use a hard-coded HD seed. + testHdSeed = chainhash.Hash{ + 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, + 0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4, + 0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9, + 0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, + } + + // The number of confirmations required to consider any created channel + // open. + numReqConfs = uint16(1) + + // A serializable txn for testing funding txn. + testTx = &wire.MsgTx{ + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.Hash{}, + Index: 0xffffffff, + }, + SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62}, + Sequence: 0xffffffff, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 5000000000, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + }, + LockTime: 5, + } +) + +// CreateTestChannels creates to fully populated channels to be used within +// testing fixtures. The channels will be returned as if the funding process +// has just completed. The channel itself is funded with 10 BTC, with 5 BTC +// allocated to each side. Within the channel, Alice is the initiator. The +// function also returns a "cleanup" function that is meant to be called once +// the test has been finalized. The clean up function will remote all temporary +// files created +func CreateTestChannels() (*LightningChannel, *LightningChannel, func(), error) { + channelCapacity, err := btcutil.NewAmount(10) + if err != nil { + return nil, nil, nil, err + } + + channelBal := channelCapacity / 2 + aliceDustLimit := btcutil.Amount(200) + bobDustLimit := btcutil.Amount(1300) + csvTimeoutAlice := uint32(5) + csvTimeoutBob := uint32(4) + + prevOut := &wire.OutPoint{ + Hash: chainhash.Hash(testHdSeed), + Index: 0, + } + fundingTxIn := wire.NewTxIn(prevOut, nil, nil) + + // For each party, we'll create a distinct set of keys in order to + // emulate the typical set up with live channels. + var ( + aliceKeys []*btcec.PrivateKey + bobKeys []*btcec.PrivateKey + ) + for i := 0; i < 5; i++ { + key := make([]byte, len(testWalletPrivKey)) + copy(key[:], testWalletPrivKey[:]) + key[0] ^= byte(i + 1) + + aliceKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), key) + aliceKeys = append(aliceKeys, aliceKey) + + key = make([]byte, len(bobsPrivKey)) + copy(key[:], bobsPrivKey) + key[0] ^= byte(i + 1) + + bobKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), key) + bobKeys = append(bobKeys, bobKey) + } + + aliceCfg := channeldb.ChannelConfig{ + ChannelConstraints: channeldb.ChannelConstraints{ + DustLimit: aliceDustLimit, + MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity), + ChanReserve: channelCapacity / 100, + MinHTLC: 0, + MaxAcceptedHtlcs: MaxHTLCNumber / 2, + }, + CsvDelay: uint16(csvTimeoutAlice), + MultiSigKey: keychain.KeyDescriptor{ + PubKey: aliceKeys[0].PubKey(), + }, + RevocationBasePoint: keychain.KeyDescriptor{ + PubKey: aliceKeys[1].PubKey(), + }, + PaymentBasePoint: keychain.KeyDescriptor{ + PubKey: aliceKeys[2].PubKey(), + }, + DelayBasePoint: keychain.KeyDescriptor{ + PubKey: aliceKeys[3].PubKey(), + }, + HtlcBasePoint: keychain.KeyDescriptor{ + PubKey: aliceKeys[4].PubKey(), + }, + } + bobCfg := channeldb.ChannelConfig{ + ChannelConstraints: channeldb.ChannelConstraints{ + DustLimit: bobDustLimit, + MaxPendingAmount: lnwire.NewMSatFromSatoshis(channelCapacity), + ChanReserve: channelCapacity / 100, + MinHTLC: 0, + MaxAcceptedHtlcs: MaxHTLCNumber / 2, + }, + CsvDelay: uint16(csvTimeoutBob), + MultiSigKey: keychain.KeyDescriptor{ + PubKey: bobKeys[0].PubKey(), + }, + RevocationBasePoint: keychain.KeyDescriptor{ + PubKey: bobKeys[1].PubKey(), + }, + PaymentBasePoint: keychain.KeyDescriptor{ + PubKey: bobKeys[2].PubKey(), + }, + DelayBasePoint: keychain.KeyDescriptor{ + PubKey: bobKeys[3].PubKey(), + }, + HtlcBasePoint: keychain.KeyDescriptor{ + PubKey: bobKeys[4].PubKey(), + }, + } + + bobRoot, err := chainhash.NewHash(bobKeys[0].Serialize()) + if err != nil { + return nil, nil, nil, err + } + bobPreimageProducer := shachain.NewRevocationProducer(*bobRoot) + bobFirstRevoke, err := bobPreimageProducer.AtIndex(0) + if err != nil { + return nil, nil, nil, err + } + bobCommitPoint := ComputeCommitmentPoint(bobFirstRevoke[:]) + + aliceRoot, err := chainhash.NewHash(aliceKeys[0].Serialize()) + if err != nil { + return nil, nil, nil, err + } + alicePreimageProducer := shachain.NewRevocationProducer(*aliceRoot) + aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0) + if err != nil { + return nil, nil, nil, err + } + aliceCommitPoint := ComputeCommitmentPoint(aliceFirstRevoke[:]) + + aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns(channelBal, + channelBal, &aliceCfg, &bobCfg, aliceCommitPoint, bobCommitPoint, + *fundingTxIn) + if err != nil { + return nil, nil, nil, err + } + + alicePath, err := ioutil.TempDir("", "alicedb") + dbAlice, err := channeldb.Open(alicePath) + if err != nil { + return nil, nil, nil, err + } + + bobPath, err := ioutil.TempDir("", "bobdb") + dbBob, err := channeldb.Open(bobPath) + if err != nil { + return nil, nil, nil, err + } + + estimator := &StaticFeeEstimator{24} + feePerVSize, err := estimator.EstimateFeePerVSize(1) + if err != nil { + return nil, nil, nil, err + } + feePerKw := feePerVSize.FeePerKWeight() + commitFee := calcStaticFee(0) + + aliceCommit := channeldb.ChannelCommitment{ + CommitHeight: 0, + LocalBalance: lnwire.NewMSatFromSatoshis(channelBal - commitFee), + RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal), + CommitFee: commitFee, + FeePerKw: btcutil.Amount(feePerKw), + CommitTx: aliceCommitTx, + CommitSig: bytes.Repeat([]byte{1}, 71), + } + bobCommit := channeldb.ChannelCommitment{ + CommitHeight: 0, + LocalBalance: lnwire.NewMSatFromSatoshis(channelBal), + RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal - commitFee), + CommitFee: commitFee, + FeePerKw: btcutil.Amount(feePerKw), + CommitTx: bobCommitTx, + CommitSig: bytes.Repeat([]byte{1}, 71), + } + + var chanIDBytes [8]byte + if _, err := io.ReadFull(rand.Reader, chanIDBytes[:]); err != nil { + return nil, nil, nil, err + } + + shortChanID := lnwire.NewShortChanIDFromInt( + binary.BigEndian.Uint64(chanIDBytes[:]), + ) + + aliceChannelState := &channeldb.OpenChannel{ + LocalChanCfg: aliceCfg, + RemoteChanCfg: bobCfg, + IdentityPub: aliceKeys[0].PubKey(), + FundingOutpoint: *prevOut, + ShortChanID: shortChanID, + ChanType: channeldb.SingleFunder, + IsInitiator: true, + Capacity: channelCapacity, + RemoteCurrentRevocation: bobCommitPoint, + RevocationProducer: alicePreimageProducer, + RevocationStore: shachain.NewRevocationStore(), + LocalCommitment: aliceCommit, + RemoteCommitment: aliceCommit, + Db: dbAlice, + Packager: channeldb.NewChannelPackager(shortChanID), + FundingTxn: testTx, + } + bobChannelState := &channeldb.OpenChannel{ + LocalChanCfg: bobCfg, + RemoteChanCfg: aliceCfg, + IdentityPub: bobKeys[0].PubKey(), + FundingOutpoint: *prevOut, + ShortChanID: shortChanID, + ChanType: channeldb.SingleFunder, + IsInitiator: false, + Capacity: channelCapacity, + RemoteCurrentRevocation: aliceCommitPoint, + RevocationProducer: bobPreimageProducer, + RevocationStore: shachain.NewRevocationStore(), + LocalCommitment: bobCommit, + RemoteCommitment: bobCommit, + Db: dbBob, + Packager: channeldb.NewChannelPackager(shortChanID), + } + + aliceSigner := &mockSigner{privkeys: aliceKeys} + bobSigner := &mockSigner{privkeys: bobKeys} + + pCache := &mockPreimageCache{ + // hash -> preimage + preimageMap: make(map[[32]byte][]byte), + } + + // TODO(roasbeef): make mock version of pre-image store + channelAlice, err := NewLightningChannel( + aliceSigner, pCache, aliceChannelState, + ) + if err != nil { + return nil, nil, nil, err + } + channelBob, err := NewLightningChannel( + bobSigner, pCache, bobChannelState, + ) + if err != nil { + return nil, nil, nil, err + } + + err = SetStateNumHint( + aliceCommitTx, 0, channelAlice.stateHintObfuscator, + ) + if err != nil { + return nil, nil, nil, err + } + err = SetStateNumHint( + bobCommitTx, 0, channelAlice.stateHintObfuscator, + ) + if err != nil { + return nil, nil, nil, err + } + + if err := channelAlice.channelState.FullSync(); err != nil { + return nil, nil, nil, err + } + if err := channelBob.channelState.FullSync(); err != nil { + return nil, nil, nil, err + } + + cleanUpFunc := func() { + os.RemoveAll(bobPath) + os.RemoveAll(alicePath) + + channelAlice.Stop() + channelBob.Stop() + } + + // Now that the channel are open, simulate the start of a session by + // having Alice and Bob extend their revocation windows to each other. + err = initRevocationWindows(channelAlice, channelBob) + if err != nil { + return nil, nil, nil, err + } + + return channelAlice, channelBob, cleanUpFunc, nil +} + +// initRevocationWindows simulates a new channel being opened within the p2p +// network by populating the initial revocation windows of the passed +// commitment state machines. +func initRevocationWindows(chanA, chanB *LightningChannel) error { + aliceNextRevoke, err := chanA.NextRevocationKey() + if err != nil { + return err + } + if err := chanB.InitNextRevocation(aliceNextRevoke); err != nil { + return err + } + + bobNextRevoke, err := chanB.NextRevocationKey() + if err != nil { + return err + } + if err := chanA.InitNextRevocation(bobNextRevoke); err != nil { + return err + } + + return nil +} + +// mockSigner is a simple implementation of the Signer interface. Each one has +// a set of private keys in a slice and can sign messages using the appropriate +// one. +type mockSigner struct { + privkeys []*btcec.PrivateKey + netParams *chaincfg.Params +} + +func (m *mockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error) { + pubkey := signDesc.KeyDesc.PubKey + switch { + case signDesc.SingleTweak != nil: + pubkey = TweakPubKeyWithTweak(pubkey, signDesc.SingleTweak) + case signDesc.DoubleTweak != nil: + pubkey = DeriveRevocationPubkey(pubkey, signDesc.DoubleTweak.PubKey()) + } + + hash160 := btcutil.Hash160(pubkey.SerializeCompressed()) + privKey := m.findKey(hash160, signDesc.SingleTweak, signDesc.DoubleTweak) + if privKey == nil { + return nil, fmt.Errorf("Mock signer does not have key") + } + + sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes, + signDesc.InputIndex, signDesc.Output.Value, signDesc.WitnessScript, + txscript.SigHashAll, privKey) + if err != nil { + return nil, err + } + + return sig[:len(sig)-1], nil +} + +func (m *mockSigner) ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) { + scriptType, addresses, _, err := txscript.ExtractPkScriptAddrs( + signDesc.Output.PkScript, m.netParams) + if err != nil { + return nil, err + } + + switch scriptType { + case txscript.PubKeyHashTy: + privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, + signDesc.DoubleTweak) + if privKey == nil { + return nil, fmt.Errorf("Mock signer does not have key for "+ + "address %v", addresses[0]) + } + + scriptSig, err := txscript.SignatureScript(tx, signDesc.InputIndex, + signDesc.Output.PkScript, txscript.SigHashAll, privKey, true) + if err != nil { + return nil, err + } + + return &InputScript{ScriptSig: scriptSig}, nil + + case txscript.WitnessV0PubKeyHashTy: + privKey := m.findKey(addresses[0].ScriptAddress(), signDesc.SingleTweak, + signDesc.DoubleTweak) + if privKey == nil { + return nil, fmt.Errorf("Mock signer does not have key for "+ + "address %v", addresses[0]) + } + + witnessScript, err := txscript.WitnessSignature(tx, signDesc.SigHashes, + signDesc.InputIndex, signDesc.Output.Value, + signDesc.Output.PkScript, txscript.SigHashAll, privKey, true) + if err != nil { + return nil, err + } + + return &InputScript{Witness: witnessScript}, nil + + default: + return nil, fmt.Errorf("Unexpected script type: %v", scriptType) + } +} + +// findKey searches through all stored private keys and returns one +// corresponding to the hashed pubkey if it can be found. The public key may +// either correspond directly to the private key or to the private key with a +// tweak applied. +func (m *mockSigner) findKey(needleHash160 []byte, singleTweak []byte, + doubleTweak *btcec.PrivateKey) *btcec.PrivateKey { + + for _, privkey := range m.privkeys { + // First check whether public key is directly derived from private key. + hash160 := btcutil.Hash160(privkey.PubKey().SerializeCompressed()) + if bytes.Equal(hash160, needleHash160) { + return privkey + } + + // Otherwise check if public key is derived from tweaked private key. + switch { + case singleTweak != nil: + privkey = TweakPrivKey(privkey, singleTweak) + case doubleTweak != nil: + privkey = DeriveRevocationPrivKey(privkey, doubleTweak) + default: + continue + } + hash160 = btcutil.Hash160(privkey.PubKey().SerializeCompressed()) + if bytes.Equal(hash160, needleHash160) { + return privkey + } + } + return nil +} + +type mockPreimageCache struct { + sync.Mutex + preimageMap map[[32]byte][]byte +} + +func (m *mockPreimageCache) LookupPreimage(hash []byte) ([]byte, bool) { + m.Lock() + defer m.Unlock() + + var h [32]byte + copy(h[:], hash) + + p, ok := m.preimageMap[h] + return p, ok +} + +func (m *mockPreimageCache) AddPreimage(preimage []byte) error { + m.Lock() + defer m.Unlock() + + m.preimageMap[sha256.Sum256(preimage[:])] = preimage + + return nil +} + +// pubkeyFromHex parses a Bitcoin public key from a hex encoded string. +func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) { + bytes, err := hex.DecodeString(keyHex) + if err != nil { + return nil, err + } + return btcec.ParsePubKey(bytes, btcec.S256()) +} + +// privkeyFromHex parses a Bitcoin private key from a hex encoded string. +func privkeyFromHex(keyHex string) (*btcec.PrivateKey, error) { + bytes, err := hex.DecodeString(keyHex) + if err != nil { + return nil, err + } + key, _ := btcec.PrivKeyFromBytes(btcec.S256(), bytes) + return key, nil + +} + +// pubkeyToHex serializes a Bitcoin public key to a hex encoded string. +func pubkeyToHex(key *btcec.PublicKey) string { + return hex.EncodeToString(key.SerializeCompressed()) +} + +// privkeyFromHex serializes a Bitcoin private key to a hex encoded string. +func privkeyToHex(key *btcec.PrivateKey) string { + return hex.EncodeToString(key.Serialize()) +} + +// signatureFromHex parses a Bitcoin signature from a hex encoded string. +func signatureFromHex(sigHex string) (*btcec.Signature, error) { + bytes, err := hex.DecodeString(sigHex) + if err != nil { + return nil, err + } + return btcec.ParseSignature(bytes, btcec.S256()) +} + +// blockFromHex parses a full Bitcoin block from a hex encoded string. +func blockFromHex(blockHex string) (*btcutil.Block, error) { + bytes, err := hex.DecodeString(blockHex) + if err != nil { + return nil, err + } + return btcutil.NewBlockFromBytes(bytes) +} + +// txFromHex parses a full Bitcoin transaction from a hex encoded string. +func txFromHex(txHex string) (*btcutil.Tx, error) { + bytes, err := hex.DecodeString(txHex) + if err != nil { + return nil, err + } + return btcutil.NewTxFromBytes(bytes) +} + +// calcStaticFee calculates appropriate fees for commitment transactions. This +// function provides a simple way to allow test balance assertions to take fee +// calculations into account. +// +// TODO(bvu): Refactor when dynamic fee estimation is added. +func calcStaticFee(numHTLCs int) btcutil.Amount { + const ( + commitWeight = btcutil.Amount(724) + htlcWeight = 172 + feePerKw = btcutil.Amount(24/4) * 1000 + ) + return feePerKw * (commitWeight + + btcutil.Amount(htlcWeight*numHTLCs)) / 1000 +} diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index 42d5859cc..3ab47f2d9 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -419,7 +419,7 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) { // of the dependencies. channel := LightningChannel{ channelState: &channelState, - signer: signer, + Signer: signer, localChanCfg: &channelState.LocalChanCfg, remoteChanCfg: &channelState.RemoteChanCfg, }