breacharbiter: sweep incoming + outgoing htlcs

This commit also adds a BreachConfig to abstract
  the instantiation of the breach arbiter, as well
  as various formatting improvements.
This commit is contained in:
Conner Fromknecht 2017-09-01 03:11:14 -07:00
parent b64d4356c1
commit a8d667ba35
No known key found for this signature in database
GPG Key ID: 39DE78FBE6ACB0EF

@ -30,6 +30,52 @@ import (
// continue from the persisted state. // continue from the persisted state.
var retributionBucket = []byte("retribution") var retributionBucket = []byte("retribution")
// BreachConfig bundles the required subsystems used by the breach arbiter. An
// instance of BreachConfig is passed to newBreachArbiter during instantiation.
type BreachConfig struct {
// Signer is used by the breach arbiter to generate sweep transactions,
// which move coins from previously open channels back to the user's
// wallet.
Signer lnwallet.Signer
// DB provides access to the user's channels, allowing the breach
// arbiter to determine the current state of a user's channels, and how
// it should respond to channel closure.
DB *channeldb.DB
// PublishTransaction facilitates the process of broadcasting a
// transaction to the network.
PublishTransaction func(*wire.MsgTx) error
// Notifier provides a publish/subscribe interface for event driven
// notifications regarding the confirmation of txids.
Notifier chainntnfs.ChainNotifier
// ChainIO is used by the breach arbiter to determine the current height
// of the blockchain, which is required to subscribe for spend
// notifications from Notifier.
ChainIO lnwallet.BlockChainIO
// Estimator is used by the breach arbiter to determine an appropriate
// fee level when generating, signing, and broadcasting sweep
// transactions.
Estimator lnwallet.FeeEstimator
// CloseLink allows the breach arbiter to shutdown any channel links for
// which it detects a breach, ensuring now further activity will
// continue across the link. The method accepts link's channel point and a
// close type to be included in the channel close summary.
CloseLink func(*wire.OutPoint, htlcswitch.ChannelCloseType)
// Store is a persistent resource that maintains information regarding
// breached channels. This is used in conjunction with DB to recover
// from crashes, restarts, or other failures.
Store RetributionStore
// GenSweepScript generates the receiving scripts for swept outputs.
GenSweepScript func() ([]byte, error)
}
// breachArbiter is a special subsystem which is responsible for watching and // breachArbiter is a special subsystem which is responsible for watching and
// acting on the detection of any attempted uncooperative channel breaches by // acting on the detection of any attempted uncooperative channel breaches by
// channel counterparties. This file essentially acts as deterrence code for // channel counterparties. This file essentially acts as deterrence code for
@ -39,15 +85,7 @@ var retributionBucket = []byte("retribution")
// counterparties. // counterparties.
// TODO(roasbeef): closures in config for subsystem pointers to decouple? // TODO(roasbeef): closures in config for subsystem pointers to decouple?
type breachArbiter struct { type breachArbiter struct {
wallet *lnwallet.LightningWallet cfg *BreachConfig
signer lnwallet.Signer
db *channeldb.DB
notifier chainntnfs.ChainNotifier
chainIO lnwallet.BlockChainIO
estimator lnwallet.FeeEstimator
htlcSwitch *htlcswitch.Switch
retributionStore RetributionStore
// breachObservers is a map which tracks all the active breach // breachObservers is a map which tracks all the active breach
// observers we're currently managing. The key of the map is the // observers we're currently managing. The key of the map is the
@ -82,20 +120,9 @@ type breachArbiter struct {
// newBreachArbiter creates a new instance of a breachArbiter initialized with // newBreachArbiter creates a new instance of a breachArbiter initialized with
// its dependent objects. // its dependent objects.
func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB, func newBreachArbiter(cfg *BreachConfig) *breachArbiter {
notifier chainntnfs.ChainNotifier, h *htlcswitch.Switch,
chain lnwallet.BlockChainIO, fe lnwallet.FeeEstimator) *breachArbiter {
return &breachArbiter{ return &breachArbiter{
wallet: wallet, cfg: cfg,
signer: wallet.Cfg.Signer,
db: db,
notifier: notifier,
chainIO: chain,
htlcSwitch: h,
estimator: fe,
retributionStore: newRetributionStore(db),
breachObservers: make(map[wire.OutPoint]chan struct{}), breachObservers: make(map[wire.OutPoint]chan struct{}),
breachedContracts: make(chan *retributionInfo), breachedContracts: make(chan *retributionInfo),
@ -121,7 +148,7 @@ func (b *breachArbiter) Start() error {
// breach is reflected in channeldb. // breach is reflected in channeldb.
breachRetInfos := make(map[wire.OutPoint]retributionInfo) breachRetInfos := make(map[wire.OutPoint]retributionInfo)
closeSummaries := make(map[wire.OutPoint]channeldb.ChannelCloseSummary) closeSummaries := make(map[wire.OutPoint]channeldb.ChannelCloseSummary)
err := b.retributionStore.ForAll(func(ret *retributionInfo) error { err := b.cfg.Store.ForAll(func(ret *retributionInfo) error {
// Extract emitted retribution information. // Extract emitted retribution information.
breachRetInfos[ret.chanPoint] = *ret breachRetInfos[ret.chanPoint] = *ret
@ -148,7 +175,7 @@ func (b *breachArbiter) Start() error {
// We need to query that database state for all currently active // We need to query that database state for all currently active
// channels, each of these channels will need a goroutine assigned to // channels, each of these channels will need a goroutine assigned to
// it to watch for channel breaches. // it to watch for channel breaches.
activeChannels, err := b.db.FetchAllChannels() activeChannels, err := b.cfg.DB.FetchAllChannels()
if err != nil && err != channeldb.ErrNoActiveChannels { if err != nil && err != channeldb.ErrNoActiveChannels {
brarLog.Errorf("unable to fetch active channels: %v", err) brarLog.Errorf("unable to fetch active channels: %v", err)
return err return err
@ -185,8 +212,9 @@ func (b *breachArbiter) Start() error {
channelsToWatch := make([]*lnwallet.LightningChannel, 0, nActive) channelsToWatch := make([]*lnwallet.LightningChannel, 0, nActive)
for _, chanState := range activeChannels { for _, chanState := range activeChannels {
// Initialize active channel from persisted channel state. // Initialize active channel from persisted channel state.
channel, err := lnwallet.NewLightningChannel(nil, b.notifier, channel, err := lnwallet.NewLightningChannel(
b.estimator, chanState) nil, b.cfg.Notifier, b.cfg.Estimator, chanState,
)
if err != nil { if err != nil {
brarLog.Errorf("unable to load channel from "+ brarLog.Errorf("unable to load channel from "+
"disk: %v", err) "disk: %v", err)
@ -205,10 +233,8 @@ func (b *breachArbiter) Start() error {
// notify the HTLC switch that this link should be // notify the HTLC switch that this link should be
// closed, and that all activity on the link should // closed, and that all activity on the link should
// cease. // cease.
b.htlcSwitch.CloseLink( b.cfg.CloseLink(&chanState.FundingOutpoint,
&chanState.FundingOutpoint, htlcswitch.CloseBreach)
htlcswitch.CloseBreach,
)
// Ensure channeldb is consistent with the persisted // Ensure channeldb is consistent with the persisted
// breach. // breach.
@ -231,7 +257,7 @@ func (b *breachArbiter) Start() error {
} }
// TODO(roasbeef): instead use closure height of channel // TODO(roasbeef): instead use closure height of channel
_, currentHeight, err := b.chainIO.GetBestBlock() _, currentHeight, err := b.cfg.ChainIO.GetBestBlock()
if err != nil { if err != nil {
return err return err
} }
@ -249,7 +275,7 @@ func (b *breachArbiter) Start() error {
// Register for a notification when the breach transaction is // Register for a notification when the breach transaction is
// confirmed on chain. // confirmed on chain.
breachTXID := closeSummary.ClosingTXID breachTXID := closeSummary.ClosingTXID
confChan, err := b.notifier.RegisterConfirmationsNtfn( confChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn(
&breachTXID, 1, uint32(currentHeight)) &breachTXID, 1, uint32(currentHeight))
if err != nil { if err != nil {
brarLog.Errorf("unable to register for conf updates "+ brarLog.Errorf("unable to register for conf updates "+
@ -293,7 +319,7 @@ func (b *breachArbiter) watchForPendingCloseConfs(currentHeight int32) error {
pendingClose.ChanPoint) pendingClose.ChanPoint)
closeTXID := pendingClose.ClosingTXID closeTXID := pendingClose.ClosingTXID
confNtfn, err := b.notifier.RegisterConfirmationsNtfn( confNtfn, err := b.cfg.Notifier.RegisterConfirmationsNtfn(
&closeTXID, 1, uint32(currentHeight), &closeTXID, 1, uint32(currentHeight),
) )
if err != nil { if err != nil {
@ -321,7 +347,7 @@ func (b *breachArbiter) watchForPendingCloseConfs(currentHeight int32) error {
// UnilateralCloseSummary on disk so can // UnilateralCloseSummary on disk so can
// possibly sweep output here // possibly sweep output here
err := b.db.MarkChanFullyClosed(&chanPoint) err := b.cfg.DB.MarkChanFullyClosed(&chanPoint)
if err != nil { if err != nil {
brarLog.Errorf("unable to mark channel"+ brarLog.Errorf("unable to mark channel"+
" as closed: %v", err) " as closed: %v", err)
@ -385,7 +411,7 @@ out:
for { for {
select { select {
case breachInfo := <-b.breachedContracts: case breachInfo := <-b.breachedContracts:
_, currentHeight, err := b.chainIO.GetBestBlock() _, currentHeight, err := b.cfg.ChainIO.GetBestBlock()
if err != nil { if err != nil {
brarLog.Errorf("unable to get best height: %v", brarLog.Errorf("unable to get best height: %v",
err) err)
@ -397,7 +423,7 @@ out:
// transaction) has been confirmed in the chain to // transaction) has been confirmed in the chain to
// ensure we're not dealing with a moving target. // ensure we're not dealing with a moving target.
breachTXID := &breachInfo.commitHash breachTXID := &breachInfo.commitHash
confChan, err := b.notifier.RegisterConfirmationsNtfn( cfChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn(
breachTXID, 1, uint32(currentHeight), breachTXID, 1, uint32(currentHeight),
) )
if err != nil { if err != nil {
@ -417,7 +443,7 @@ out:
// retribution after the breach transaction has been // retribution after the breach transaction has been
// confirmed. // confirmed.
b.wg.Add(1) b.wg.Add(1)
go b.exactRetribution(confChan, breachInfo) go b.exactRetribution(cfChan, breachInfo)
delete(b.breachObservers, breachInfo.chanPoint) delete(b.breachObservers, breachInfo.chanPoint)
@ -523,7 +549,7 @@ func (b *breachArbiter) exactRetribution(
return spew.Sdump(justiceTx) return spew.Sdump(justiceTx)
})) }))
_, currentHeight, err := b.chainIO.GetBestBlock() _, currentHeight, err := b.cfg.ChainIO.GetBestBlock()
if err != nil { if err != nil {
brarLog.Errorf("unable to get current height: %v", err) brarLog.Errorf("unable to get current height: %v", err)
return return
@ -531,7 +557,7 @@ func (b *breachArbiter) exactRetribution(
// Finally, broadcast the transaction, finalizing the channels' // Finally, broadcast the transaction, finalizing the channels'
// retribution against the cheating counterparty. // retribution against the cheating counterparty.
if err := b.wallet.PublishTransaction(justiceTx); err != nil { if err := b.cfg.PublishTransaction(justiceTx); err != nil {
brarLog.Errorf("unable to broadcast "+ brarLog.Errorf("unable to broadcast "+
"justice tx: %v", err) "justice tx: %v", err)
return return
@ -542,8 +568,8 @@ func (b *breachArbiter) exactRetribution(
// notify the caller that initiated the retribution workflow that the // notify the caller that initiated the retribution workflow that the
// deed has been done. // deed has been done.
justiceTXID := justiceTx.TxHash() justiceTXID := justiceTx.TxHash()
confChan, err = b.notifier.RegisterConfirmationsNtfn(&justiceTXID, 1, confChan, err = b.cfg.Notifier.RegisterConfirmationsNtfn(
uint32(currentHeight)) &justiceTXID, 1, uint32(currentHeight))
if err != nil { if err != nil {
brarLog.Errorf("unable to register for conf for txid: %v", brarLog.Errorf("unable to register for conf for txid: %v",
justiceTXID) justiceTXID)
@ -566,14 +592,14 @@ func (b *breachArbiter) exactRetribution(
revokedFunds, totalFunds) revokedFunds, totalFunds)
// With the channel closed, mark it in the database as such. // With the channel closed, mark it in the database as such.
err := b.db.MarkChanFullyClosed(&breachInfo.chanPoint) err := b.cfg.DB.MarkChanFullyClosed(&breachInfo.chanPoint)
if err != nil { if err != nil {
brarLog.Errorf("unable to mark chan as closed: %v", err) brarLog.Errorf("unable to mark chan as closed: %v", err)
} }
// Justice has been carried out; we can safely delete the // Justice has been carried out; we can safely delete the
// retribution info from the database. // retribution info from the database.
err = b.retributionStore.Remove(&breachInfo.chanPoint) err = b.cfg.Store.Remove(&breachInfo.chanPoint)
if err != nil { if err != nil {
brarLog.Errorf("unable to remove retribution "+ brarLog.Errorf("unable to remove retribution "+
"from the db: %v", err) "from the db: %v", err)
@ -640,7 +666,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
// TODO(roasbeef): also notify utxoNursery, might've had // TODO(roasbeef): also notify utxoNursery, might've had
// outbound HTLC's in flight // outbound HTLC's in flight
go waitForChanToClose(uint32(closeInfo.SpendingHeight), go waitForChanToClose(uint32(closeInfo.SpendingHeight),
b.notifier, nil, chanPoint, closeInfo.SpenderTxHash, b.cfg.Notifier, nil, chanPoint, closeInfo.SpenderTxHash,
func() { func() {
// As we just detected a channel was closed via // As we just detected a channel was closed via
// a unilateral commitment broadcast by the // a unilateral commitment broadcast by the
@ -661,13 +687,11 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
goto close goto close
} }
brarLog.Infof("Sweeping %v breached "+ brarLog.Infof("Sweeping breached "+
"outputs with: %v", "outputs with: %v",
spew.Sdump(sweepTx)) spew.Sdump(sweepTx))
err = b.wallet.PublishTransaction( err = b.cfg.PublishTransaction(sweepTx)
sweepTx,
)
if err != nil { if err != nil {
brarLog.Errorf("unable to "+ brarLog.Errorf("unable to "+
"broadcast tx: %v", err) "broadcast tx: %v", err)
@ -679,7 +703,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
"is fully closed, updating DB", "is fully closed, updating DB",
chanPoint) chanPoint)
err := b.db.MarkChanFullyClosed(chanPoint) err := b.cfg.DB.MarkChanFullyClosed(chanPoint)
if err != nil { if err != nil {
brarLog.Errorf("unable to mark chan "+ brarLog.Errorf("unable to mark chan "+
"as closed: %v", err) "as closed: %v", err)
@ -699,7 +723,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
// breached in order to ensure any incoming or outgoing // breached in order to ensure any incoming or outgoing
// multi-hop HTLCs aren't sent over this link, nor any other // multi-hop HTLCs aren't sent over this link, nor any other
// links associated with this peer. // links associated with this peer.
b.htlcSwitch.CloseLink(chanPoint, htlcswitch.CloseBreach) b.cfg.CloseLink(chanPoint, htlcswitch.CloseBreach)
// TODO(roasbeef): need to handle case of remote broadcast // TODO(roasbeef): need to handle case of remote broadcast
// mid-local initiated state-transition, possible // mid-local initiated state-transition, possible
@ -715,7 +739,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
retInfo := newRetributionInfo(chanPoint, breachInfo, chanInfo) retInfo := newRetributionInfo(chanPoint, breachInfo, chanInfo)
// Persist the pending retribution state to disk. // Persist the pending retribution state to disk.
if err := b.retributionStore.Add(retInfo); err != nil { if err := b.cfg.Store.Add(retInfo); err != nil {
brarLog.Errorf("unable to persist retribution info "+ brarLog.Errorf("unable to persist retribution info "+
"to db: %v", err) "to db: %v", err)
} }
@ -795,7 +819,7 @@ type breachedOutput struct {
amt btcutil.Amount amt btcutil.Amount
outpoint wire.OutPoint outpoint wire.OutPoint
witnessType lnwallet.WitnessType witnessType lnwallet.WitnessType
signDesc *lnwallet.SignDescriptor signDesc lnwallet.SignDescriptor
witnessFunc lnwallet.WitnessGenerator witnessFunc lnwallet.WitnessGenerator
} }
@ -812,7 +836,7 @@ func newBreachedOutput(outpoint *wire.OutPoint,
amt: btcutil.Amount(amount), amt: btcutil.Amount(amount),
outpoint: *outpoint, outpoint: *outpoint,
witnessType: witnessType, witnessType: witnessType,
signDesc: signDescriptor, signDesc: *signDescriptor,
} }
} }
@ -841,8 +865,7 @@ func (bo *breachedOutput) BuildWitness(signer lnwallet.Signer,
// been initialized for this breached output. // been initialized for this breached output.
if bo.witnessFunc == nil { if bo.witnessFunc == nil {
bo.witnessFunc = bo.witnessType.GenWitnessFunc( bo.witnessFunc = bo.witnessType.GenWitnessFunc(
signer, signer, &bo.signDesc,
bo.signDesc,
) )
} }
@ -923,13 +946,21 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
// deterministically generate a valid witness for each output. This will // deterministically generate a valid witness for each output. This will
// allow the breach arbiter to recover from failures, in the event that // allow the breach arbiter to recover from failures, in the event that
// it must sign and broadcast the justice transaction. // it must sign and broadcast the justice transaction.
var htlcOutputs = make([]*breachedOutput, nHtlcs) htlcOutputs := make([]*breachedOutput, nHtlcs)
for i, breachedHtlc := range breachInfo.HtlcRetributions { for i, breachedHtlc := range breachInfo.HtlcRetributions {
// Using the breachedHtlc's incoming flag, determine the
// appropriate witness type that needs to be generated in order
// to sweep the HTLC output.
var htlcWitnessType lnwallet.WitnessType
if breachedHtlc.IsIncoming {
htlcWitnessType = lnwallet.HtlcAcceptedRevoke
} else {
htlcWitnessType = lnwallet.HtlcOfferedRevoke
}
htlcOutputs[i] = newBreachedOutput( htlcOutputs[i] = newBreachedOutput(
&breachedHtlc.OutPoint, &breachInfo.HtlcRetributions[i].OutPoint, htlcWitnessType,
lnwallet.CommitmentRevoke, &breachInfo.HtlcRetributions[i].SignDesc)
&breachedHtlc.SignDesc,
)
} }
// TODO(conner): remove dependency on channel snapshot after decoupling // TODO(conner): remove dependency on channel snapshot after decoupling
@ -960,24 +991,35 @@ func (b *breachArbiter) createJusticeTx(
// Assemble the breached outputs into a slice of spendable outputs, // Assemble the breached outputs into a slice of spendable outputs,
// starting with the self and revoked outputs, then adding any htlc // starting with the self and revoked outputs, then adding any htlc
// outputs. // outputs.
var breachedOutputs = make([]SpendableOutput, 2+nHtlcs) breachedOutputs := make([]SpendableOutput, 2+nHtlcs)
breachedOutputs[0] = r.selfOutput breachedOutputs[0] = r.selfOutput
breachedOutputs[1] = r.revokedOutput breachedOutputs[1] = r.revokedOutput
for i, htlcOutput := range r.htlcOutputs { for i, htlcOutput := range r.htlcOutputs {
breachedOutputs[2+i] = htlcOutput breachedOutputs[2+i] = htlcOutput
} }
// Compute the transaction weight of the justice transaction, which
// includes 2 + nHtlcs inputs and one output.
var txWeight uint64 var txWeight uint64
// Begin with a base txn weight, e.g. version, nLockTime, etc. // Begin with a base txn weight, e.g. version, nLockTime, etc.
txWeight += 4*lnwallet.BaseSweepTxSize + lnwallet.WitnessHeaderSize txWeight += 4*lnwallet.BaseSweepTxSize + lnwallet.WitnessHeaderSize
// Add to_local revoke script and tx input. // Add to_local revoke script and tx input.
txWeight += 4*lnwallet.InputSize + lnwallet.ToLocalPenaltyWitnessSize txWeight += 4*lnwallet.InputSize + lnwallet.ToLocalPenaltyWitnessSize
// Add to_remote p2wpkh witness and tx input. // Add to_remote p2wpkh witness and tx input.
txWeight += 4*lnwallet.InputSize + lnwallet.P2WKHWitnessSize txWeight += 4*lnwallet.InputSize + lnwallet.P2WKHWitnessSize
// Add revoked offered-htlc witnesses and tx inputs.
txWeight += uint64(len(r.htlcOutputs)) * // Compute the appropriate weight contributed by each revoked accepted
(4*lnwallet.InputSize + lnwallet.OfferedHtlcWitnessSize) // or offered HTLC witnesses and tx inputs.
for _, htlcOutput := range r.htlcOutputs {
switch htlcOutput.witnessType {
case lnwallet.HtlcOfferedRevoke:
txWeight += 4*lnwallet.InputSize +
lnwallet.OfferedHtlcPenaltyWitnessSize
case lnwallet.HtlcAcceptedRevoke:
txWeight += 4*lnwallet.InputSize +
lnwallet.AcceptedHtlcPenaltyWitnessSize
}
}
return b.sweepSpendableOutputsTxn(txWeight, breachedOutputs...) return b.sweepSpendableOutputsTxn(txWeight, breachedOutputs...)
} }
@ -999,6 +1041,8 @@ func (b *breachArbiter) craftCommitSweepTx(
closeInfo.SelfOutputSignDesc, closeInfo.SelfOutputSignDesc,
) )
// Compute the transaction weight of the commit sweep transaction, which
// includes a single input and output.
var txWeight uint64 var txWeight uint64
// Begin with a base txn weight, e.g. version, nLockTime, etc. // Begin with a base txn weight, e.g. version, nLockTime, etc.
txWeight += 4*lnwallet.BaseSweepTxSize + lnwallet.WitnessHeaderSize txWeight += 4*lnwallet.BaseSweepTxSize + lnwallet.WitnessHeaderSize
@ -1017,7 +1061,7 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight uint64,
// sweep the funds to. // sweep the funds to.
// TODO(roasbeef): possibly create many outputs to minimize change in // TODO(roasbeef): possibly create many outputs to minimize change in
// the future? // the future?
pkScript, err := newSweepPkScript(b.wallet) pkScript, err := b.cfg.GenSweepScript()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1028,14 +1072,14 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight uint64,
totalAmt += input.Amount() totalAmt += input.Amount()
} }
feePerWeight := b.estimator.EstimateFeePerWeight(1) feePerWeight := b.cfg.Estimator.EstimateFeePerWeight(1)
txFee := btcutil.Amount(txWeight * feePerWeight) txFee := btcutil.Amount(txWeight * feePerWeight)
sweepAmt := int64(totalAmt - txFee) sweepAmt := int64(totalAmt - txFee)
// With the fee calculated, we can now create the transaction using the // With the fee calculated, we can now create the transaction using the
// information gathered above and the provided retribution information. // information gathered above and the provided retribution information.
var txn = wire.NewMsgTx(2) txn := wire.NewMsgTx(2)
// We begin by adding the output to which our funds will be deposited. // We begin by adding the output to which our funds will be deposited.
txn.AddTxOut(&wire.TxOut{ txn.AddTxOut(&wire.TxOut{
@ -1072,7 +1116,7 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight uint64,
// transaction using the SpendableOutput's witness generation // transaction using the SpendableOutput's witness generation
// function. // function.
witness, err := so.BuildWitness( witness, err := so.BuildWitness(
b.wallet.Cfg.Signer, txn, hashCache, idx, b.cfg.Signer, txn, hashCache, idx,
) )
if err != nil { if err != nil {
return err return err
@ -1339,7 +1383,7 @@ func (bo *breachedOutput) Encode(w io.Writer) error {
return err return err
} }
if err := lnwallet.WriteSignDescriptor(w, bo.signDesc); err != nil { if err := lnwallet.WriteSignDescriptor(w, &bo.signDesc); err != nil {
return err return err
} }
@ -1364,8 +1408,7 @@ func (bo *breachedOutput) Decode(r io.Reader) error {
return err return err
} }
bo.signDesc = &lnwallet.SignDescriptor{} if err := lnwallet.ReadSignDescriptor(r, &bo.signDesc); err != nil {
if err := lnwallet.ReadSignDescriptor(r, bo.signDesc); err != nil {
return err return err
} }