mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
498 lines
16 KiB
Go
498 lines
16 KiB
Go
package main
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/roasbeef/btcd/txscript"
|
|
"github.com/roasbeef/btcd/wire"
|
|
"github.com/roasbeef/btcutil"
|
|
)
|
|
|
|
// breachArbiter is a special sub-system which is responsible for watching and
|
|
// acting on the detection of any attempted uncooperative channel breaches by
|
|
// channel counter-parties. This file essentially acts as deterrence code for
|
|
// those attempting to launch attacks against the daemon. In practice it's
|
|
// expected that the logic in this file never gets executed, but it is
|
|
// important to have it in place just in case we encounter cheating channel
|
|
// counter-parties.
|
|
// TODO(roasbeef): closures in config for sub-system pointers to decouple?
|
|
type breachArbiter struct {
|
|
wallet *lnwallet.LightningWallet
|
|
db *channeldb.DB
|
|
notifier chainntnfs.ChainNotifier
|
|
htlcSwitch *htlcSwitch
|
|
|
|
// breachObservers is a map which tracks all the active breach
|
|
// observers we're currently managing. The key of the map is the
|
|
// funding outpoint of the channel, and the value is a channel which
|
|
// will be closed once we detect that the channel has been
|
|
// cooperatively closed, there by killing the goroutine and freeing up
|
|
// resource.
|
|
breachObservers map[wire.OutPoint]chan struct{}
|
|
|
|
// breachedContracts is a channel which is used internally within the
|
|
// struct to send the necessary information required to punish a
|
|
// counter-party once a channel breach is detected. Breach observers
|
|
// use this to communicate with the main contractObserver goroutine.
|
|
breachedContracts chan *retributionInfo
|
|
|
|
// newContracts is a channel which is used by outside sub-systems to
|
|
// notify the breachArbiter of a new contract (a channel) that should
|
|
// be watched.
|
|
newContracts chan *lnwallet.LightningChannel
|
|
|
|
// settledContracts is a channel by outside sub-subsystems to notify
|
|
// the breachArbiter that a channel has peacefully been closed. Once a
|
|
// channel has been closed the arbiter no longer needs to watch for
|
|
// breach closes.
|
|
settledContracts chan *wire.OutPoint
|
|
|
|
started uint32
|
|
stopped uint32
|
|
quit chan struct{}
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// newBreachArbiter creates a new instance of a breachArbiter initialize with
|
|
// its dependant objects.
|
|
func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB,
|
|
notifier chainntnfs.ChainNotifier, h *htlcSwitch) *breachArbiter {
|
|
|
|
return &breachArbiter{
|
|
wallet: wallet,
|
|
db: db,
|
|
notifier: notifier,
|
|
htlcSwitch: h,
|
|
|
|
breachObservers: make(map[wire.OutPoint]chan struct{}),
|
|
breachedContracts: make(chan *retributionInfo),
|
|
newContracts: make(chan *lnwallet.LightningChannel),
|
|
settledContracts: make(chan *wire.OutPoint),
|
|
quit: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// Start is an idempotent method that officially starts the breachArbiter along
|
|
// with all other goroutines it needs to perform its functions.
|
|
func (b *breachArbiter) Start() error {
|
|
if !atomic.CompareAndSwapUint32(&b.started, 0, 1) {
|
|
return nil
|
|
}
|
|
|
|
brarLog.Tracef("Starting breach aribter")
|
|
|
|
b.wg.Add(1)
|
|
go b.contractObserver()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stop is an idempotent method that signals the breachArbiter to execute a
|
|
// graceful shutdown. This function will block until all goroutines spawned by
|
|
// the breachArbiter have gracefully exited.
|
|
func (b *breachArbiter) Stop() error {
|
|
if !atomic.CompareAndSwapUint32(&b.stopped, 0, 1) {
|
|
return nil
|
|
}
|
|
|
|
brarLog.Infof("Breach arbiter shutting down")
|
|
|
|
close(b.quit)
|
|
b.wg.Wait()
|
|
|
|
return nil
|
|
}
|
|
|
|
// contractObserver is the primary goroutine for the breachArbiter. This
|
|
// goroutine is responsible for managing goroutines that watch for breaches for
|
|
// all current active and newly created channels. If a channel breach is
|
|
// detected by a spawned child goroutine, then the contractObserver will
|
|
// execute the retribution logic required to sweep ALL outputs from a contested
|
|
// channel into the daemon's wallet.
|
|
//
|
|
// NOTE: This MUST be run as a goroutine.
|
|
func (b *breachArbiter) contractObserver() {
|
|
defer b.wg.Done()
|
|
|
|
// First we need to query that database state for all currently active
|
|
// channels, each of these channels will need a goroutine assigned to
|
|
// it to watch for channel breaches.
|
|
activeChannels, err := b.db.FetchAllChannels()
|
|
if err != nil {
|
|
// TODO(roasbeef): this is a fatal error...
|
|
brarLog.Errorf("unable to fetch active channels: %v", err)
|
|
}
|
|
|
|
brarLog.Infof("Retrieved %v channels from database, watching with "+
|
|
"vigilance!", len(activeChannels))
|
|
|
|
// For each active channel found within the database, we launch a
|
|
// detected breachObserver goroutine for that channel and also track
|
|
// the new goroutine within the breachObservers map so we can cancel it
|
|
// later if necessary.
|
|
for _, chanState := range activeChannels {
|
|
channel, err := lnwallet.NewLightningChannel(nil, nil,
|
|
b.notifier, chanState)
|
|
if err != nil {
|
|
brarLog.Errorf("unable to load channel: %v", err)
|
|
}
|
|
|
|
settleSignal := make(chan struct{})
|
|
chanPoint := channel.ChannelPoint()
|
|
b.breachObservers[*chanPoint] = settleSignal
|
|
|
|
// TODO(roasbeef): possibility of state divergence if updates
|
|
// conducted after re-connect, need to ensure only one instance
|
|
// is watched at all times
|
|
b.wg.Add(1)
|
|
go b.breachObserver(channel, settleSignal)
|
|
}
|
|
|
|
out:
|
|
for {
|
|
select {
|
|
case breachInfo := <-b.breachedContracts:
|
|
// A new channel contract has just been breached! We
|
|
// first register for a notification to be dispatched
|
|
// once the breach transaction (the revoked commitment
|
|
// transaction) has been confirmed in the chain to
|
|
// ensure we're not dealing with a moving target.
|
|
breachTXID := &breachInfo.commitHash
|
|
confChan, err := b.notifier.RegisterConfirmationsNtfn(breachTXID, 1)
|
|
if err != nil {
|
|
brarLog.Errorf("unable to register for conf for txid: ",
|
|
breachTXID)
|
|
continue
|
|
}
|
|
|
|
brarLog.Warnf("A channel has been breached with tx: %v. "+
|
|
"Waiting for confirmation, then justice will be served!",
|
|
breachTXID)
|
|
|
|
// With the notification registered, we launch a new
|
|
// goroutine which will finalize the channel
|
|
// retribution after the breach transaction has been
|
|
// confirmed.
|
|
b.wg.Add(1)
|
|
go b.exactRetribution(confChan, breachInfo)
|
|
|
|
delete(b.breachObservers, breachInfo.chanPoint)
|
|
case contract := <-b.newContracts:
|
|
// A new channel has just been opened within the
|
|
// daemon, so we launch a new breachObserver to handle
|
|
// the detection of attempted contract breaches.
|
|
settleSignal := make(chan struct{})
|
|
chanPoint := contract.ChannelPoint()
|
|
b.breachObservers[*chanPoint] = settleSignal
|
|
|
|
brarLog.Tracef("New contract detected, launching " +
|
|
"breachObserver")
|
|
|
|
b.wg.Add(1)
|
|
go b.breachObserver(contract, settleSignal)
|
|
|
|
case chanPoint := <-b.settledContracts:
|
|
// A new channel has been closed either unilaterally or
|
|
// cooperatively, as a result we no longer need a
|
|
// breachObserver detected to the channel.
|
|
killSignal, ok := b.breachObservers[*chanPoint]
|
|
if !ok {
|
|
brarLog.Errorf("Unable to find contract: %v",
|
|
chanPoint)
|
|
continue
|
|
}
|
|
|
|
brarLog.Debugf("ChannelPoint(%v) has been settled, "+
|
|
"cancelling breachObserver", chanPoint)
|
|
|
|
// If we had a breachObserver active, then we signal it
|
|
// for exit and also delete its state from our tracking
|
|
// map.
|
|
close(killSignal)
|
|
delete(b.breachObservers, *chanPoint)
|
|
case <-b.quit:
|
|
break out
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// exactRetribution is a goroutine which is executed once a contract breach has
|
|
// been detected by a breachObserver. This function is responsible for
|
|
// punishing a counter-party for violating the channel contract by sweeping ALL
|
|
// the lingering funds within the channel into the daemon's wallet.
|
|
//
|
|
// NOTE: This MUST be run as a goroutine.
|
|
func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent,
|
|
breachInfo *retributionInfo) {
|
|
|
|
defer b.wg.Done()
|
|
|
|
// TODO(roasbeef): state needs to be check-pointed here
|
|
|
|
select {
|
|
case _, ok := <-confChan.Confirmed:
|
|
// If the second value is !ok, then the channel has been closed
|
|
// signifying a daemon shutdown, so we exit.
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// Otherwise, if this is a real confirmation notification, then
|
|
// we fall through to complete out duty.
|
|
case <-b.quit:
|
|
return
|
|
}
|
|
|
|
brarLog.Debugf("Breach transaction %v has been confirmed, sweeping "+
|
|
"revoked funds", breachInfo.commitHash)
|
|
|
|
// With the breach transaction confirmed, we now create the justice tx
|
|
// which will claim ALL the funds within the channel.
|
|
justiceTx, err := b.createJusticeTx(breachInfo)
|
|
if err != nil {
|
|
brarLog.Errorf("unable to create justice tx: %v", err)
|
|
return
|
|
}
|
|
|
|
brarLog.Debugf("Broadcasting justice tx: %v", newLogClosure(func() string {
|
|
return spew.Sdump(justiceTx)
|
|
}))
|
|
|
|
// Finally, broadcast the transaction, finalizing the channels'
|
|
// retribution against the cheating counter-party.
|
|
if err := b.wallet.PublishTransaction(justiceTx); err != nil {
|
|
brarLog.Errorf("unable to broadcast "+
|
|
"justice tx: %v", err)
|
|
return
|
|
}
|
|
|
|
// As a conclusionary step, we register for a notification to be
|
|
// dispatched once the justice tx is confirmed. After confirmation we
|
|
// notify the caller that initiated the retribution work low that the
|
|
// deed has been done.
|
|
justiceTXID := justiceTx.TxSha()
|
|
confChan, err = b.notifier.RegisterConfirmationsNtfn(&justiceTXID, 1)
|
|
if err != nil {
|
|
brarLog.Errorf("unable to register for conf for txid: %v",
|
|
justiceTXID)
|
|
return
|
|
}
|
|
|
|
select {
|
|
case _, ok := <-confChan.Confirmed:
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
// TODO(roasbeef): factor in HTLC's
|
|
revokedFunds := breachInfo.revokedOutput.amt
|
|
totalFunds := revokedFunds + breachInfo.selfOutput.amt
|
|
|
|
brarLog.Infof("Justice for ChannelPoint(%v) has "+
|
|
"been served, %v revoked funds (%v total) "+
|
|
"have been claimed", breachInfo.chanPoint,
|
|
revokedFunds, totalFunds)
|
|
|
|
// TODO(roasbeef): add peer to blacklist?
|
|
|
|
// TODO(roasbeef): close other active channels with offending peer
|
|
|
|
close(breachInfo.doneChan)
|
|
|
|
return
|
|
case <-b.quit:
|
|
return
|
|
}
|
|
}
|
|
|
|
// breachObserver notifies the breachArbiter contract observer goroutine that a
|
|
// channel's contract has been breached by the prior counter party. Once
|
|
// notified the breachArbiter will attempt to sweep ALL funds within the
|
|
// channel using the information provided within the BreachRetribution
|
|
// generated due to the breach of channel contract. The funds will be swept
|
|
// only after the breaching transaction receives a necessary number of
|
|
// confirmations.
|
|
func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel,
|
|
settleSignal chan struct{}) {
|
|
|
|
defer b.wg.Done()
|
|
|
|
chanPoint := contract.ChannelPoint()
|
|
|
|
brarLog.Debugf("Breach observer for ChannelPoint(%v) started", chanPoint)
|
|
|
|
select {
|
|
// A read from this channel indicates that the contract has been
|
|
// settled cooperatively so we exit as our duties are no longer needed.
|
|
case <-settleSignal:
|
|
return
|
|
|
|
// A read from this channel indicates that a channel breach has been
|
|
// detected! So we notify the main coordination goroutine with the
|
|
// information needed to bring the counter-party to justice.
|
|
case breachInfo := <-contract.ContractBreach:
|
|
brarLog.Warnf("REVOKED STATE #%v FOR ChannelPoint(%v) "+
|
|
"broadcast, REMOTE PEER IS DOING SOMETHING "+
|
|
"SKETCHY!!!", breachInfo.RevokedStateNum,
|
|
chanPoint)
|
|
|
|
// Immediately notify the HTLC switch that this link has been
|
|
// breached in order to ensure any incoming or outgoing
|
|
// multi-hop HTLC's aren't sent over this link, nor any other
|
|
// links associated with this peer.
|
|
b.htlcSwitch.CloseLink(chanPoint, CloseBreach)
|
|
if err := contract.DeleteState(); err != nil {
|
|
brarLog.Errorf("unable to delete channel state: %v", err)
|
|
}
|
|
|
|
// TODO(roasbeef): need to handle case of remote broadcast
|
|
// mid-local initiated state-transition, possible false-positive?
|
|
|
|
// First we generate the witness generation function which will
|
|
// be used to sweep the output only we can satisfy on the
|
|
// commitment transaction. This output is just a regular p2wkh
|
|
// output.
|
|
localSignDesc := breachInfo.LocalOutputSignDesc
|
|
localWitness := func(tx *wire.MsgTx, hc *txscript.TxSigHashes,
|
|
inputIndex int) ([][]byte, error) {
|
|
|
|
desc := localSignDesc
|
|
desc.SigHashes = hc
|
|
desc.InputIndex = inputIndex
|
|
|
|
return lnwallet.CommitSpendNoDelay(b.wallet.Signer, desc, tx)
|
|
}
|
|
|
|
// Next we create the witness generation function that will be
|
|
// used to sweep the cheating counter party's output by taking
|
|
// advantage of the revocation clause within the output's
|
|
// witness script.
|
|
remoteSignDesc := breachInfo.RemoteOutputSignDesc
|
|
remoteWitness := func(tx *wire.MsgTx, hc *txscript.TxSigHashes,
|
|
inputIndex int) ([][]byte, error) {
|
|
|
|
desc := breachInfo.RemoteOutputSignDesc
|
|
desc.SigHashes = hc
|
|
desc.InputIndex = inputIndex
|
|
|
|
return lnwallet.CommitSpendRevoke(b.wallet.Signer, desc, tx)
|
|
}
|
|
|
|
// Finally, with the two witness generation funcs created, we
|
|
// send the retribution information to the utxo nursery.
|
|
// TODO(roasbeef): populate htlc breacches
|
|
b.breachedContracts <- &retributionInfo{
|
|
commitHash: breachInfo.BreachTransaction.TxSha(),
|
|
chanPoint: *chanPoint,
|
|
|
|
selfOutput: &breachedOutput{
|
|
amt: btcutil.Amount(localSignDesc.Output.Value),
|
|
outpoint: breachInfo.LocalOutpoint,
|
|
witnessFunc: localWitness,
|
|
},
|
|
|
|
revokedOutput: &breachedOutput{
|
|
amt: btcutil.Amount(remoteSignDesc.Output.Value),
|
|
outpoint: breachInfo.RemoteOutpoint,
|
|
witnessFunc: remoteWitness,
|
|
},
|
|
|
|
doneChan: make(chan struct{}),
|
|
}
|
|
case <-b.quit:
|
|
return
|
|
}
|
|
}
|
|
|
|
// breachedOutput contains all the information needed to sweep a breached
|
|
// output. A breach output is an output that were now entitled to due to a
|
|
// revoked commitment transaction being broadcast.
|
|
type breachedOutput struct {
|
|
amt btcutil.Amount
|
|
outpoint wire.OutPoint
|
|
witnessFunc witnessGenerator
|
|
|
|
twoStageClaim bool
|
|
}
|
|
|
|
// retributionInfo encapsulates all the data needed to sweep all the contested
|
|
// funds within a channel whose contract has been breached by the prior
|
|
// counter-party. This struct is used by the utxoNursery to create the justice
|
|
// transaction which spends all outputs of the commitment transaction into an
|
|
// output controlled by the wallet.
|
|
type retributionInfo struct {
|
|
commitHash wire.ShaHash
|
|
chanPoint wire.OutPoint
|
|
|
|
selfOutput *breachedOutput
|
|
|
|
revokedOutput *breachedOutput
|
|
|
|
htlcOutputs *[]breachedOutput
|
|
|
|
doneChan chan struct{}
|
|
}
|
|
|
|
// createJusticeTx creates a transaction which exacts "justice" by sweeping ALL
|
|
// the funds within the channel which we are now entitled to due to a breach of
|
|
// the channel's contract by the counter-party. This function returns a *fully*
|
|
// signed transaction with the witness for each input fully in place.
|
|
func (b *breachArbiter) createJusticeTx(r *retributionInfo) (*wire.MsgTx, error) {
|
|
// First, we obtain a new public key script from the wallet which we'll
|
|
// sweep the funds to.
|
|
// TODO(roasbeef): possibly create many outputs to minimize change in
|
|
// the future?
|
|
pkScriptOfJustice, err := newSweepPkScript(b.wallet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Before creating the actual TxOut, we'll need to calculate proper fee
|
|
// to attach to the transaction to ensure a timely confirmation.
|
|
// TODO(roasbeef): remove hard-coded fee
|
|
totalAmt := r.selfOutput.amt + r.revokedOutput.amt
|
|
sweepedAmt := int64(totalAmt - 5000)
|
|
|
|
// With the fee calculate, we can now create the justice transaction
|
|
// using the information gathered above.
|
|
justiceTx := wire.NewMsgTx()
|
|
justiceTx.AddTxOut(&wire.TxOut{
|
|
PkScript: pkScriptOfJustice,
|
|
Value: sweepedAmt,
|
|
})
|
|
justiceTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: r.selfOutput.outpoint,
|
|
})
|
|
justiceTx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: r.revokedOutput.outpoint,
|
|
})
|
|
|
|
hashCache := txscript.NewTxSigHashes(justiceTx)
|
|
|
|
// Finally, using the witness generation functions attached to the
|
|
// retribution information, we'll populate the inputs with fully valid
|
|
// witnesses for both commitment outputs, and all the pending HTLC's at
|
|
// this state in the channel's history.
|
|
// TODO(roasbeef): handle the 2-layer HTLC's
|
|
localWitness, err := r.selfOutput.witnessFunc(justiceTx, hashCache, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
justiceTx.TxIn[0].Witness = localWitness
|
|
|
|
remoteWitness, err := r.revokedOutput.witnessFunc(justiceTx, hashCache, 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
justiceTx.TxIn[1].Witness = remoteWitness
|
|
|
|
return justiceTx, nil
|
|
}
|