Merge pull request #1158 from Roasbeef/play-all-commits

contractcourt+lnwallet: ensure the chainWatcher properly plays all remote commitments
This commit is contained in:
Olaoluwa Osuntokun 2018-05-04 12:22:02 -07:00 committed by GitHub
commit bdf3f4d775
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1154 additions and 683 deletions

@ -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
}

@ -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

@ -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")
}
}

@ -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
}

@ -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)
}

@ -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)
}

592
lnwallet/test_utils.go Normal file

@ -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
}

@ -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,
}