Merge pull request #1158 from Roasbeef/play-all-commits
contractcourt+lnwallet: ensure the chainWatcher properly plays all remote commitments
This commit is contained in:
commit
bdf3f4d775
@ -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
|
||||
|
207
contractcourt/chain_watcher_test.go
Normal file
207
contractcourt/chain_watcher_test.go
Normal file
@ -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
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,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user