Merge pull request #1158 from Roasbeef/play-all-commits
contractcourt+lnwallet: ensure the chainWatcher properly plays all remote commitments
This commit is contained in:
8 files changed
+1154
-683
No files matched your search
+115
-24
@@ -1578,9 +1578,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// ChannelCloseSummary contains the final state of a channel at the point it
|
// ChannelCloseSummary contains the final state of a channel at the point it
|
||||||
// was closed. Once a channel is closed, all the information pertaining to
|
// was closed. Once a channel is closed, all the information pertaining to that
|
||||||
// that channel within the openChannelBucket is deleted, and a compact
|
// channel within the openChannelBucket is deleted, and a compact summary is
|
||||||
// summary is put in place instead.
|
// put in place instead.
|
||||||
type ChannelCloseSummary struct {
|
type ChannelCloseSummary struct {
|
||||||
// ChanPoint is the outpoint for this channel's funding transaction,
|
// ChanPoint is the outpoint for this channel's funding transaction,
|
||||||
// and is used as a unique identifier for the channel.
|
// 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 was the total capacity of the channel.
|
||||||
Capacity btcutil.Amount
|
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
|
CloseHeight uint32
|
||||||
|
|
||||||
// SettledBalance is our total balance settled balance at the time of
|
// 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
|
// closed, they'll stay marked as "pending" until _all_ the pending
|
||||||
// funds have been swept.
|
// funds have been swept.
|
||||||
IsPending bool
|
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
|
// CloseChannel closes a previously active Lightning channel. Closing a channel
|
||||||
@@ -1675,6 +1691,16 @@ func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary) error {
|
|||||||
return ErrNoActiveChannels
|
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
|
// Now that the index to this channel has been deleted, purge
|
||||||
// the remaining channel metadata from the database.
|
// the remaining channel metadata from the database.
|
||||||
err = deleteOpenChannel(chanBucket, chanPointBuf.Bytes())
|
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
|
// Finally, create a summary of this channel in the closed
|
||||||
// channel bucket for this node.
|
// 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,
|
func putChannelCloseSummary(tx *bolt.Tx, chanID []byte,
|
||||||
summary *ChannelCloseSummary) error {
|
summary *ChannelCloseSummary, lastChanState *OpenChannel) error {
|
||||||
|
|
||||||
closedChanBucket, err := tx.CreateBucketIfNotExists(closedChannelBucket)
|
closedChanBucket, err := tx.CreateBucketIfNotExists(closedChannelBucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
summary.RemoteCurrentRevocation = lastChanState.RemoteCurrentRevocation
|
||||||
|
summary.RemoteNextRevocation = lastChanState.RemoteNextRevocation
|
||||||
|
summary.LocalChanConfig = lastChanState.LocalChanCfg
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if err := serializeChannelCloseSummary(&b, summary); err != nil {
|
if err := serializeChannelCloseSummary(&b, summary); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -1834,11 +1866,37 @@ func putChannelCloseSummary(tx *bolt.Tx, chanID []byte,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error {
|
func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error {
|
||||||
return writeElements(w,
|
err := writeElements(w,
|
||||||
cs.ChanPoint, cs.ShortChanID, cs.ChainHash, cs.ClosingTXID,
|
cs.ChanPoint, cs.ShortChanID, cs.ChainHash, cs.ClosingTXID,
|
||||||
cs.CloseHeight, cs.RemotePub, cs.Capacity, cs.SettledBalance,
|
cs.CloseHeight, cs.RemotePub, cs.Capacity, cs.SettledBalance,
|
||||||
cs.TimeLockedBalance, cs.CloseType, cs.IsPending,
|
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,
|
func fetchChannelCloseSummary(tx *bolt.Tx,
|
||||||
@@ -1870,9 +1928,49 @@ func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) {
|
|||||||
return nil, err
|
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
|
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 {
|
func putChanInfo(chanBucket *bolt.Bucket, channel *OpenChannel) error {
|
||||||
var w bytes.Buffer
|
var w bytes.Buffer
|
||||||
if err := writeElements(&w,
|
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 {
|
if err := writeChanConfig(&w, &channel.LocalChanCfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1976,6 +2066,16 @@ func putChanRevocationState(chanBucket *bolt.Bucket, channel *OpenChannel) error
|
|||||||
return chanBucket.Put(revocationStateKey, b.Bytes())
|
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 {
|
func fetchChanInfo(chanBucket *bolt.Bucket, channel *OpenChannel) error {
|
||||||
infoBytes := chanBucket.Get(chanInfoKey)
|
infoBytes := chanBucket.Get(chanInfoKey)
|
||||||
if infoBytes == nil {
|
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 {
|
if err := readChanConfig(r, &channel.LocalChanCfg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,40 +340,71 @@ func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
|
|||||||
)
|
)
|
||||||
remoteStateNum := remoteCommit.CommitHeight
|
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 {
|
switch {
|
||||||
// If state number spending transaction matches the
|
// If state number spending transaction matches the
|
||||||
// current latest state, then they've initiated a
|
// current latest state, then they've initiated a
|
||||||
// unilateral close. So we'll trigger the unilateral
|
// unilateral close. So we'll trigger the unilateral
|
||||||
// close signal so subscribers can clean up the state
|
// close signal so subscribers can clean up the state
|
||||||
// as necessary.
|
// 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
|
// We'll also handle the case of the remote party
|
||||||
// broadcasting their commitment transaction which is
|
// broadcasting their commitment transaction which is
|
||||||
// one height above ours. This case can arise when we
|
// one height above ours. This case can arise when we
|
||||||
// initiate a state transition, but the remote party
|
// initiate a state transition, but the remote party
|
||||||
// has a fail crash _after_ accepting the new state,
|
// has a fail crash _after_ accepting the new state,
|
||||||
// but _before_ sending their signature to us.
|
// but _before_ sending their signature to us.
|
||||||
case broadcastStateNum >= remoteStateNum:
|
case broadcastStateNum == remoteStateNum+1 &&
|
||||||
if err := c.dispatchRemoteForceClose(
|
remoteChainTip != nil:
|
||||||
commitSpend, *remoteCommit,
|
|
||||||
); err != nil {
|
err := c.dispatchRemoteForceClose(
|
||||||
|
commitSpend, remoteChainTip.Commitment,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
log.Errorf("unable to handle remote "+
|
log.Errorf("unable to handle remote "+
|
||||||
"close for chan_point=%v: %v",
|
"close for chan_point=%v: %v",
|
||||||
c.cfg.chanState.FundingOutpoint, err)
|
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
|
// If the state number broadcast is lower than the
|
||||||
// remote node's current un-revoked height, then
|
// remote node's current un-revoked height, then
|
||||||
// THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT
|
// THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT
|
||||||
// WITHIN THE PAYMENT CHANNEL. Therefore we close the
|
// WITHIN THE PAYMENT CHANNEL. Therefore we close the
|
||||||
// signal indicating a revoked broadcast to allow
|
// signal indicating a revoked broadcast to allow
|
||||||
// subscribers to
|
// subscribers to swiftly dispatch justice!!!
|
||||||
// swiftly dispatch justice!!!
|
|
||||||
case broadcastStateNum < remoteStateNum:
|
case broadcastStateNum < remoteStateNum:
|
||||||
if err := c.dispatchContractBreach(
|
err := c.dispatchContractBreach(
|
||||||
commitSpend, remoteCommit,
|
commitSpend, remoteCommit,
|
||||||
broadcastStateNum,
|
broadcastStateNum,
|
||||||
); err != nil {
|
)
|
||||||
|
if err != nil {
|
||||||
log.Errorf("unable to handle channel "+
|
log.Errorf("unable to handle channel "+
|
||||||
"breach for chan_point=%v: %v",
|
"breach for chan_point=%v: %v",
|
||||||
c.cfg.chanState.FundingOutpoint, err)
|
c.cfg.chanState.FundingOutpoint, err)
|
||||||
@@ -570,13 +601,16 @@ func (c *chainWatcher) dispatchLocalForceClose(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// dispatchRemoteForceClose processes a detected unilateral channel closure by the
|
// dispatchRemoteForceClose processes a detected unilateral channel closure by
|
||||||
// remote party. This function will prepare a UnilateralCloseSummary which will
|
// the remote party. This function will prepare a UnilateralCloseSummary which
|
||||||
// then be sent to any subscribers allowing them to resolve all our funds in
|
// will then be sent to any subscribers allowing them to resolve all our funds
|
||||||
// the channel on chain. Once this close summary is prepared, all registered
|
// in the channel on chain. Once this close summary is prepared, all registered
|
||||||
// subscribers will receive a notification of this event.
|
// 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,
|
func (c *chainWatcher) dispatchRemoteForceClose(commitSpend *chainntnfs.SpendDetail,
|
||||||
remoteCommit channeldb.ChannelCommitment) error {
|
remoteCommit channeldb.ChannelCommitment, isRemotePendingCommit bool) error {
|
||||||
|
|
||||||
log.Infof("Unilateral close of ChannelPoint(%v) "+
|
log.Infof("Unilateral close of ChannelPoint(%v) "+
|
||||||
"detected", c.cfg.chanState.FundingOutpoint)
|
"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
|
// First, we'll create a closure summary that contains all the
|
||||||
// materials required to let each subscriber sweep the funds in the
|
// materials required to let each subscriber sweep the funds in the
|
||||||
// channel on-chain.
|
// channel on-chain.
|
||||||
uniClose, err := lnwallet.NewUnilateralCloseSummary(c.cfg.chanState,
|
uniClose, err := lnwallet.NewUnilateralCloseSummary(
|
||||||
c.cfg.signer, c.cfg.pCache, commitSpend, remoteCommit,
|
c.cfg.chanState, c.cfg.signer, c.cfg.pCache, commitSpend,
|
||||||
|
remoteCommit, isRemotePendingCommit,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package contractcourt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/roasbeef/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockNotifier struct {
|
||||||
|
spendChan chan *chainntnfs.SpendDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash, numConfs,
|
||||||
|
heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (m *mockNotifier) RegisterBlockEpochNtfn() (*chainntnfs.BlockEpochEvent, error) {
|
||||||
|
return &chainntnfs.BlockEpochEvent{
|
||||||
|
Epochs: make(chan *chainntnfs.BlockEpoch),
|
||||||
|
Cancel: func() {},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNotifier) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockNotifier) Stop() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
|
||||||
|
heightHint uint32, _ bool) (*chainntnfs.SpendEvent, error) {
|
||||||
|
return &chainntnfs.SpendEvent{
|
||||||
|
Spend: m.spendChan,
|
||||||
|
Cancel: func() {},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChainWatcherRemoteUnilateralClose tests that the chain watcher is able
|
||||||
|
// to properly detect a normal unilateral close by the remote node using their
|
||||||
|
// lowest commitment.
|
||||||
|
func TestChainWatcherRemoteUnilateralClose(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// First, we'll create two channels which already have established a
|
||||||
|
// commitment contract between themselves.
|
||||||
|
aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
|
}
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
// With the channels created, we'll now create a chain watcher instance
|
||||||
|
// which will be watching for any closes of Alice's channel.
|
||||||
|
aliceNotifier := &mockNotifier{
|
||||||
|
spendChan: make(chan *chainntnfs.SpendDetail),
|
||||||
|
}
|
||||||
|
aliceChainWatcher, err := newChainWatcher(chainWatcherConfig{
|
||||||
|
chanState: aliceChannel.State(),
|
||||||
|
notifier: aliceNotifier,
|
||||||
|
signer: aliceChannel.Signer,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create chain watcher: %v", err)
|
||||||
|
}
|
||||||
|
err = aliceChainWatcher.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to start chain watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer aliceChainWatcher.Stop()
|
||||||
|
|
||||||
|
// We'll request a new channel event subscription from Alice's chain
|
||||||
|
// watcher.
|
||||||
|
chanEvents := aliceChainWatcher.SubscribeChannelEvents()
|
||||||
|
|
||||||
|
// If we simulate an immediate broadcast of the current commitment by
|
||||||
|
// Bob, then the chain watcher should detect this case.
|
||||||
|
bobCommit := bobChannel.State().LocalCommitment.CommitTx
|
||||||
|
bobTxHash := bobCommit.TxHash()
|
||||||
|
bobSpend := &chainntnfs.SpendDetail{
|
||||||
|
SpenderTxHash: &bobTxHash,
|
||||||
|
SpendingTx: bobCommit,
|
||||||
|
}
|
||||||
|
aliceNotifier.spendChan <- bobSpend
|
||||||
|
|
||||||
|
// We should get a new spend event over the remote unilateral close
|
||||||
|
// event channel.
|
||||||
|
var uniClose *lnwallet.UnilateralCloseSummary
|
||||||
|
select {
|
||||||
|
case uniClose = <-chanEvents.RemoteUnilateralClosure:
|
||||||
|
case <-time.After(time.Second * 15):
|
||||||
|
t.Fatalf("didn't receive unilateral close event")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The unilateral close should have properly located Alice's output in
|
||||||
|
// the commitment transaction.
|
||||||
|
if uniClose.CommitResolution == nil {
|
||||||
|
t.Fatalf("unable to find alice's commit resolution")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChainWatcherRemoteUnilateralClosePendingCommit tests that the chain
|
||||||
|
// watcher is able to properly detect a unilateral close wherein the remote
|
||||||
|
// node broadcasts their newly received commitment, without first revoking the
|
||||||
|
// old one.
|
||||||
|
func TestChainWatcherRemoteUnilateralClosePendingCommit(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// First, we'll create two channels which already have established a
|
||||||
|
// commitment contract between themselves.
|
||||||
|
aliceChannel, bobChannel, cleanUp, err := lnwallet.CreateTestChannels()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
|
}
|
||||||
|
defer cleanUp()
|
||||||
|
|
||||||
|
// With the channels created, we'll now create a chain watcher instance
|
||||||
|
// which will be watching for any closes of Alice's channel.
|
||||||
|
aliceNotifier := &mockNotifier{
|
||||||
|
spendChan: make(chan *chainntnfs.SpendDetail),
|
||||||
|
}
|
||||||
|
aliceChainWatcher, err := newChainWatcher(chainWatcherConfig{
|
||||||
|
chanState: aliceChannel.State(),
|
||||||
|
notifier: aliceNotifier,
|
||||||
|
signer: aliceChannel.Signer,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create chain watcher: %v", err)
|
||||||
|
}
|
||||||
|
if err := aliceChainWatcher.Start(); err != nil {
|
||||||
|
t.Fatalf("unable to start chain watcher: %v", err)
|
||||||
|
}
|
||||||
|
defer aliceChainWatcher.Stop()
|
||||||
|
|
||||||
|
// We'll request a new channel event subscription from Alice's chain
|
||||||
|
// watcher.
|
||||||
|
chanEvents := aliceChainWatcher.SubscribeChannelEvents()
|
||||||
|
|
||||||
|
// Next, we'll create a fake HTLC just so we can advance Alice's
|
||||||
|
// channel state to a new pending commitment on her remote commit chain
|
||||||
|
// for Bob.
|
||||||
|
htlcAmount := lnwire.NewMSatFromSatoshis(20000)
|
||||||
|
preimage := bytes.Repeat([]byte{byte(1)}, 32)
|
||||||
|
paymentHash := sha256.Sum256(preimage)
|
||||||
|
var returnPreimage [32]byte
|
||||||
|
copy(returnPreimage[:], preimage)
|
||||||
|
htlc := &lnwire.UpdateAddHTLC{
|
||||||
|
ID: uint64(0),
|
||||||
|
PaymentHash: paymentHash,
|
||||||
|
Amount: htlcAmount,
|
||||||
|
Expiry: uint32(5),
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := aliceChannel.AddHTLC(htlc, nil); err != nil {
|
||||||
|
t.Fatalf("alice unable to add htlc: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := bobChannel.ReceiveHTLC(htlc); err != nil {
|
||||||
|
t.Fatalf("bob unable to recv add htlc: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// With the HTLC added, we'll now manually initiate a state transition
|
||||||
|
// from Alice to Bob.
|
||||||
|
_, _, err = aliceChannel.SignNextCommitment()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point, we'll now Bob broadcasting this new pending unrevoked
|
||||||
|
// commitment.
|
||||||
|
bobPendingCommit, err := aliceChannel.State().RemoteCommitChainTip()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll craft a fake spend notification with Bob's actual commitment.
|
||||||
|
// The chain watcher should be able to detect that this is a pending
|
||||||
|
// commit broadcast based on the state hints in the commitment.
|
||||||
|
bobCommit := bobPendingCommit.Commitment.CommitTx
|
||||||
|
bobTxHash := bobCommit.TxHash()
|
||||||
|
bobSpend := &chainntnfs.SpendDetail{
|
||||||
|
SpenderTxHash: &bobTxHash,
|
||||||
|
SpendingTx: bobCommit,
|
||||||
|
}
|
||||||
|
aliceNotifier.spendChan <- bobSpend
|
||||||
|
|
||||||
|
// We should get a new spend event over the remote unilateral close
|
||||||
|
// event channel.
|
||||||
|
var uniClose *lnwallet.UnilateralCloseSummary
|
||||||
|
select {
|
||||||
|
case uniClose = <-chanEvents.RemoteUnilateralClosure:
|
||||||
|
case <-time.After(time.Second * 15):
|
||||||
|
t.Fatalf("didn't receive unilateral close event")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The unilateral close should have properly located Alice's output in
|
||||||
|
// the commitment transaction.
|
||||||
|
if uniClose.CommitResolution == nil {
|
||||||
|
t.Fatalf("unable to find alice's commit resolution")
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
-10
@@ -1234,10 +1234,10 @@ func compactLogs(ourLog, theirLog *updateLog,
|
|||||||
//
|
//
|
||||||
// See the individual comments within the above methods for further details.
|
// See the individual comments within the above methods for further details.
|
||||||
type LightningChannel struct {
|
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
|
// signing any HTLC and commitment transaction generated by the state
|
||||||
// machine.
|
// machine.
|
||||||
signer Signer
|
Signer Signer
|
||||||
|
|
||||||
// signDesc is the primary sign descriptor that is capable of signing
|
// signDesc is the primary sign descriptor that is capable of signing
|
||||||
// the commitment transaction that spends the multi-sig output.
|
// the commitment transaction that spends the multi-sig output.
|
||||||
@@ -1350,7 +1350,7 @@ func NewLightningChannel(signer Signer, pCache PreimageCache,
|
|||||||
lc := &LightningChannel{
|
lc := &LightningChannel{
|
||||||
// TODO(roasbeef): tune num sig workers?
|
// TODO(roasbeef): tune num sig workers?
|
||||||
sigPool: newSigPool(runtime.NumCPU(), signer),
|
sigPool: newSigPool(runtime.NumCPU(), signer),
|
||||||
signer: signer,
|
Signer: signer,
|
||||||
pCache: pCache,
|
pCache: pCache,
|
||||||
currentHeight: localCommit.CommitHeight,
|
currentHeight: localCommit.CommitHeight,
|
||||||
remoteCommitChain: newCommitmentChain(remoteCommit.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 new commitment transaction while we're waiting for the rest of
|
||||||
// the HTLC signatures to be processed.
|
// the HTLC signatures to be processed.
|
||||||
lc.signDesc.SigHashes = txscript.NewTxSigHashes(newCommitView.txn)
|
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 {
|
if err != nil {
|
||||||
close(cancelChan)
|
close(cancelChan)
|
||||||
return sig, htlcSigs, err
|
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
|
// With this, we then generate the full witness so the caller can
|
||||||
// broadcast a fully signed transaction.
|
// broadcast a fully signed transaction.
|
||||||
lc.signDesc.SigHashes = txscript.NewTxSigHashes(commitTx)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -4678,14 +4678,24 @@ type UnilateralCloseSummary struct {
|
|||||||
|
|
||||||
// NewUnilateralCloseSummary creates a new summary that provides the caller
|
// NewUnilateralCloseSummary creates a new summary that provides the caller
|
||||||
// with all the information required to claim all funds on chain in the event
|
// 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,
|
func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer Signer,
|
||||||
pCache PreimageCache, commitSpend *chainntnfs.SpendDetail,
|
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
|
// 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
|
commitPoint := chanState.RemoteCurrentRevocation
|
||||||
|
if remotePendingCommit {
|
||||||
|
commitPoint = chanState.RemoteNextRevocation
|
||||||
|
}
|
||||||
keyRing := deriveCommitmentKeys(
|
keyRing := deriveCommitmentKeys(
|
||||||
commitPoint, false, &chanState.LocalChanCfg,
|
commitPoint, false, &chanState.LocalChanCfg,
|
||||||
&chanState.RemoteChanCfg,
|
&chanState.RemoteChanCfg,
|
||||||
@@ -5258,7 +5268,7 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) {
|
|||||||
|
|
||||||
localCommitment := lc.channelState.LocalCommitment
|
localCommitment := lc.channelState.LocalCommitment
|
||||||
summary, err := NewLocalForceCloseSummary(lc.channelState,
|
summary, err := NewLocalForceCloseSummary(lc.channelState,
|
||||||
lc.signer, lc.pCache, commitTx, localCommitment)
|
lc.Signer, lc.pCache, commitTx, localCommitment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// using the generated txid to be notified once the closure transaction
|
||||||
// has been confirmed.
|
// has been confirmed.
|
||||||
lc.signDesc.SigHashes = txscript.NewTxSigHashes(closeTx)
|
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 {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
+167
-424
@@ -2,122 +2,21 @@ package lnwallet
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"os"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/shachain"
|
|
||||||
"github.com/roasbeef/btcd/blockchain"
|
"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/txscript"
|
||||||
"github.com/roasbeef/btcd/wire"
|
"github.com/roasbeef/btcd/wire"
|
||||||
"github.com/roasbeef/btcutil"
|
"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
|
// forceStateTransition executes the necessary interaction between the two
|
||||||
// commitment state machines to transition to a new state locking in any
|
// commitment state machines to transition to a new state locking in any
|
||||||
// pending updates.
|
// pending updates.
|
||||||
@@ -157,277 +56,6 @@ func forceStateTransition(chanA, chanB *LightningChannel) error {
|
|||||||
return nil
|
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
|
// createHTLC is a utility function for generating an HTLC with a given
|
||||||
// preimage and a given amount.
|
// preimage and a given amount.
|
||||||
func createHTLC(id int, amount lnwire.MilliSatoshi) (*lnwire.UpdateAddHTLC, [32]byte) {
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(3)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
}
|
}
|
||||||
@@ -1048,7 +676,7 @@ func TestForceClose(t *testing.T) {
|
|||||||
Value: htlcResolution.SweepSignDesc.Output.Value,
|
Value: htlcResolution.SweepSignDesc.Output.Value,
|
||||||
})
|
})
|
||||||
htlcResolution.SweepSignDesc.InputIndex = 0
|
htlcResolution.SweepSignDesc.InputIndex = 0
|
||||||
sweepTx.TxIn[0].Witness, err = htlcSpendSuccess(aliceChannel.signer,
|
sweepTx.TxIn[0].Witness, err = htlcSpendSuccess(aliceChannel.Signer,
|
||||||
&htlcResolution.SweepSignDesc, sweepTx,
|
&htlcResolution.SweepSignDesc, sweepTx,
|
||||||
uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay))
|
uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1109,7 +737,7 @@ func TestForceClose(t *testing.T) {
|
|||||||
Value: inHtlcResolution.SweepSignDesc.Output.Value,
|
Value: inHtlcResolution.SweepSignDesc.Output.Value,
|
||||||
})
|
})
|
||||||
inHtlcResolution.SweepSignDesc.InputIndex = 0
|
inHtlcResolution.SweepSignDesc.InputIndex = 0
|
||||||
sweepTx.TxIn[0].Witness, err = htlcSpendSuccess(aliceChannel.signer,
|
sweepTx.TxIn[0].Witness, err = htlcSpendSuccess(aliceChannel.Signer,
|
||||||
&inHtlcResolution.SweepSignDesc, sweepTx,
|
&inHtlcResolution.SweepSignDesc, sweepTx,
|
||||||
uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay))
|
uint32(aliceChannel.channelState.LocalChanCfg.CsvDelay))
|
||||||
if err != nil {
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(3)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(3)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(3)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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,
|
// Create a test channel funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC. Alice's dustlimit is 200 sat, while
|
// and Bob having 5 BTC. Alice's dustlimit is 200 sat, while
|
||||||
// Bob has 1300 sat.
|
// Bob has 1300 sat.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(3)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(3)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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)
|
t.Fatalf("unable to fetch channel: %v", err)
|
||||||
}
|
}
|
||||||
aliceChannelNew, err := NewLightningChannel(
|
aliceChannelNew, err := NewLightningChannel(
|
||||||
aliceChannel.signer, nil, aliceChannels[0],
|
aliceChannel.Signer, nil, aliceChannels[0],
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new channel: %v", err)
|
t.Fatalf("unable to create new channel: %v", err)
|
||||||
}
|
}
|
||||||
bobChannelNew, err := NewLightningChannel(
|
bobChannelNew, err := NewLightningChannel(
|
||||||
bobChannel.signer, nil, bobChannels[0],
|
bobChannel.Signer, nil, bobChannels[0],
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new channel: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(5)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(5)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
}
|
}
|
||||||
@@ -2283,7 +1911,7 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
|
|||||||
func TestUpdateFeeAdjustments(t *testing.T) {
|
func TestUpdateFeeAdjustments(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
}
|
}
|
||||||
@@ -2338,7 +1966,7 @@ func TestUpdateFeeAdjustments(t *testing.T) {
|
|||||||
func TestUpdateFeeFail(t *testing.T) {
|
func TestUpdateFeeFail(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// We'll kick off the test by creating our channels which both are
|
||||||
// loaded with 5 BTC each.
|
// loaded with 5 BTC each.
|
||||||
aliceChannel, _, cleanUp, err := createTestChannels(1)
|
aliceChannel, _, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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)
|
t.Fatalf("unable to fetch channel: %v", err)
|
||||||
}
|
}
|
||||||
aliceChannelNew, err := NewLightningChannel(
|
aliceChannelNew, err := NewLightningChannel(
|
||||||
aliceChannel.signer, nil, aliceChannels[0],
|
aliceChannel.Signer, nil, aliceChannels[0],
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new channel: %v", err)
|
t.Fatalf("unable to create new channel: %v", err)
|
||||||
}
|
}
|
||||||
defer aliceChannelNew.Stop()
|
defer aliceChannelNew.Stop()
|
||||||
bobChannelNew, err := NewLightningChannel(
|
bobChannelNew, err := NewLightningChannel(
|
||||||
bobChannel.signer, nil, bobChannels[0],
|
bobChannel.Signer, nil, bobChannels[0],
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create new channel: %v", err)
|
t.Fatalf("unable to create new channel: %v", err)
|
||||||
@@ -2945,7 +2573,7 @@ func restartChannel(channelOld *LightningChannel) (*LightningChannel, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
channelNew, err := NewLightningChannel(
|
channelNew, err := NewLightningChannel(
|
||||||
channelOld.signer, channelOld.pCache, nodeChannels[0],
|
channelOld.Signer, channelOld.pCache, nodeChannels[0],
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, _, cleanUp, err := createTestChannels(1)
|
aliceChannel, _, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, _, cleanUp, err := createTestChannels(1)
|
aliceChannel, _, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
}
|
}
|
||||||
@@ -4307,7 +3935,7 @@ func TestLockedInHtlcForwardingSkipAfterRestart(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// First, we'll make a channel between Alice and Bob.
|
// First, we'll make a channel between Alice and Bob.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
}
|
}
|
||||||
@@ -4447,7 +4075,7 @@ func TestInvalidCommitSigError(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// First, we'll make a channel between Alice and Bob.
|
// First, we'll make a channel between Alice and Bob.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// 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,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(3)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
}
|
}
|
||||||
@@ -4545,8 +4173,8 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) {
|
|||||||
SpenderTxHash: &commitTxHash,
|
SpenderTxHash: &commitTxHash,
|
||||||
}
|
}
|
||||||
aliceCloseSummary, err := NewUnilateralCloseSummary(
|
aliceCloseSummary, err := NewUnilateralCloseSummary(
|
||||||
aliceChannel.channelState, aliceChannel.signer, aliceChannel.pCache,
|
aliceChannel.channelState, aliceChannel.Signer, aliceChannel.pCache,
|
||||||
spendDetail, aliceChannel.channelState.RemoteCommitment,
|
spendDetail, aliceChannel.channelState.RemoteCommitment, false,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create alice close summary: %v", err)
|
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
|
// With the transaction constructed, we'll generate a witness that
|
||||||
// should be valid for it, and verify using an instance of Script.
|
// should be valid for it, and verify using an instance of Script.
|
||||||
sweepTx.TxIn[0].Witness, err = receiverHtlcSpendTimeout(
|
sweepTx.TxIn[0].Witness, err = receiverHtlcSpendTimeout(
|
||||||
aliceChannel.signer, &outHtlcResolution.SweepSignDesc,
|
aliceChannel.Signer, &outHtlcResolution.SweepSignDesc,
|
||||||
sweepTx, int32(outHtlcResolution.Expiry),
|
sweepTx, int32(outHtlcResolution.Expiry),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -4622,7 +4250,7 @@ func TestChannelUnilateralCloseHtlcResolution(t *testing.T) {
|
|||||||
sweepTx,
|
sweepTx,
|
||||||
)
|
)
|
||||||
sweepTx.TxIn[0].Witness, err = SenderHtlcSpendRedeem(
|
sweepTx.TxIn[0].Witness, err = SenderHtlcSpendRedeem(
|
||||||
aliceChannel.signer, &inHtlcResolution.SweepSignDesc,
|
aliceChannel.Signer, &inHtlcResolution.SweepSignDesc,
|
||||||
sweepTx, preimageBob[:],
|
sweepTx, preimageBob[:],
|
||||||
)
|
)
|
||||||
if err != nil {
|
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
|
// TestDesyncHTLCs checks that we cannot add HTLCs that would make the
|
||||||
// balance negative, when the remote and local update logs are desynced.
|
// balance negative, when the remote and local update logs are desynced.
|
||||||
func TestDesyncHTLCs(t *testing.T) {
|
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
|
// We'll kick off the test by creating our channels which both are
|
||||||
// loaded with 5 BTC each.
|
// loaded with 5 BTC each.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// We'll kick off the test by creating our channels which both are
|
||||||
// loaded with 5 BTC each.
|
// loaded with 5 BTC each.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// We'll kick off the test by creating our channels which both are
|
||||||
// loaded with 5 BTC each.
|
// loaded with 5 BTC each.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
}
|
}
|
||||||
@@ -4838,9 +4581,9 @@ func TestChanReserve(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
setupChannels := func() (*LightningChannel, *LightningChannel, func()) {
|
setupChannels := func() (*LightningChannel, *LightningChannel, func()) {
|
||||||
// We'll kick off the test by creating our channels which both
|
// We'll kick off the test by creating our channels which both are
|
||||||
// are loaded with 5 BTC each.
|
// loaded with 5 BTC each.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// We'll kick off the test by creating our channels which both are
|
||||||
// loaded with 5 BTC each.
|
// loaded with 5 BTC each.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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
|
// We'll kick off the test by creating our channels which both are
|
||||||
// loaded with 5 BTC each.
|
// loaded with 5 BTC each.
|
||||||
aliceChannel, bobChannel, cleanUp, err := createTestChannels(1)
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
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)
|
|
||||||
}
|
|
||||||
@@ -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.
|
// of the dependencies.
|
||||||
channel := LightningChannel{
|
channel := LightningChannel{
|
||||||
channelState: &channelState,
|
channelState: &channelState,
|
||||||
signer: signer,
|
Signer: signer,
|
||||||
localChanCfg: &channelState.LocalChanCfg,
|
localChanCfg: &channelState.LocalChanCfg,
|
||||||
remoteChanCfg: &channelState.RemoteChanCfg,
|
remoteChanCfg: &channelState.RemoteChanCfg,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user