breacharbiter: introduce new sub-system to watch for breaches
This commit introduces a new sub-system into the daemon whose job it is to vigilantly watch for any potential channel breaches throughout the up-time of the daemon. The logic which was moved from the utxoNursery in a prior commit now resides within the breachArbiter. Upon start-up the breachArbiter will query the database for all active channels, launching a goroutine for each channel in order to be able to take action if a channel breach is detected. The breachArbiter is also responsible for notifying the htlcSwitch about channel breaches in order to black-list the breached linked during any multi-hop forwarding decisions.
This commit is contained in:
parent
93cbfdbd60
commit
494fcec874
495
breacharbiter.go
Normal file
495
breacharbiter.go
Normal file
@ -0,0 +1,495 @@
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
}
|
@ -13,8 +13,8 @@ import (
|
||||
"github.com/roasbeef/btcd/wire"
|
||||
"github.com/roasbeef/btcutil"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"github.com/lightningnetwork/lnd/routing/rt/graph"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -122,6 +122,8 @@ type fundingManager struct {
|
||||
// wallet is the daemon's internal Lightning enabled wallet.
|
||||
wallet *lnwallet.LightningWallet
|
||||
|
||||
breachAribter *breachArbiter
|
||||
|
||||
// fundingMsgs is a channel which receives wrapped wire messages
|
||||
// related to funding workflow from outside peers.
|
||||
fundingMsgs chan interface{}
|
||||
@ -140,10 +142,12 @@ type fundingManager struct {
|
||||
|
||||
// newFundingManager creates and initializes a new instance of the
|
||||
// fundingManager.
|
||||
func newFundingManager(w *lnwallet.LightningWallet) *fundingManager {
|
||||
func newFundingManager(w *lnwallet.LightningWallet, b *breachArbiter) *fundingManager {
|
||||
return &fundingManager{
|
||||
wallet: w,
|
||||
breachAribter: b,
|
||||
|
||||
activeReservations: make(map[int32]pendingChannels),
|
||||
wallet: w,
|
||||
fundingMsgs: make(chan interface{}, msgBufferSize),
|
||||
fundingRequests: make(chan *initFundingMsg, msgBufferSize),
|
||||
queries: make(chan interface{}, 1),
|
||||
@ -158,7 +162,7 @@ func (f *fundingManager) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
fndgLog.Infof("funding manager running")
|
||||
fndgLog.Tracef("Funding manager running")
|
||||
|
||||
f.wg.Add(1) // TODO(roasbeef): tune
|
||||
go f.reservationCoordinator()
|
||||
@ -173,7 +177,7 @@ func (f *fundingManager) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
fndgLog.Infof("funding manager shutting down")
|
||||
fndgLog.Infof("Funding manager shutting down")
|
||||
|
||||
close(f.quit)
|
||||
|
||||
@ -604,6 +608,11 @@ func (f *fundingManager) handleFundingSignComplete(fmsg *fundingSignCompleteMsg)
|
||||
// server peer.
|
||||
fmsg.peer.newChannels <- openChan
|
||||
|
||||
// Afterwards we send the breach arbiter the new
|
||||
// channel so it can watch for attempts to breach the
|
||||
// channel's contract by the remote party.
|
||||
f.breachAribter.newContracts <- openChan
|
||||
|
||||
// Next, we queue a message to notify the remote peer
|
||||
// that the channel is open. We additionally provide an
|
||||
// SPV proof allowing them to verify the transaction
|
||||
@ -700,6 +709,11 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
|
||||
},
|
||||
)
|
||||
|
||||
// Send the newly opened channel to the breach arbiter to it can watch
|
||||
// for uncopperative channel breaches, potentially punishing the
|
||||
// counter-party for attempting to cheat us.
|
||||
f.breachAribter.newContracts <- openChan
|
||||
|
||||
// Finally, notify the target peer of the newly open channel.
|
||||
fmsg.peer.newChannels <- openChan
|
||||
}
|
||||
|
@ -581,6 +581,9 @@ func (h *htlcSwitch) handleCloseLink(req *closeLinkReq) {
|
||||
hswcLog.Debugf("requesting interface %v to close link %v",
|
||||
hex.EncodeToString(targetLink.peer.lightningID[:]), req.chanPoint)
|
||||
targetLink.peer.localCloseChanReqs <- req
|
||||
|
||||
// TODO(roasbeef): if type was CloseBreach initiate force closure with
|
||||
// all other channels (if any) we have with the remote peer.
|
||||
}
|
||||
|
||||
// handleLinkUpdate processes the link info update message by adjusting the
|
||||
|
26
lnd_test.go
26
lnd_test.go
@ -1114,24 +1114,26 @@ func testRevokedCloseRetribution(net *networkHarness, t *harnessTest) {
|
||||
// broadcast as Bob's contract breaching transaction gets confirmed
|
||||
// above.
|
||||
var justiceTXID *wire.ShaHash
|
||||
breakTimeout := time.After(time.Second * 5)
|
||||
poll:
|
||||
for {
|
||||
select {
|
||||
case <-time.After(time.Second * 5):
|
||||
case <-breakTimeout:
|
||||
t.Fatalf("justice tx not found in mempool")
|
||||
default:
|
||||
mempool, err := net.Miner.Node.GetRawMempool()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get mempool: %v", err)
|
||||
}
|
||||
|
||||
if len(mempool) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
justiceTXID = mempool[0]
|
||||
break poll
|
||||
}
|
||||
|
||||
mempool, err := net.Miner.Node.GetRawMempool()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get mempool: %v", err)
|
||||
}
|
||||
|
||||
if len(mempool) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
justiceTXID = mempool[0]
|
||||
break poll
|
||||
}
|
||||
|
||||
// Query for the mempool transaction found above. Then assert that all
|
||||
|
@ -467,7 +467,7 @@ func NewLightningChannel(signer Signer, bio BlockChainIO,
|
||||
FundingWitnessScript: state.FundingWitnessScript,
|
||||
ForceCloseSignal: make(chan struct{}),
|
||||
UnilateralCloseSignal: make(chan struct{}),
|
||||
ContractBreach: make(chan *BreachRetribution),
|
||||
ContractBreach: make(chan *BreachRetribution, 1),
|
||||
}
|
||||
|
||||
// Initialize both of our chains the current un-revoked commitment for
|
||||
@ -692,6 +692,7 @@ func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEven
|
||||
lc.Lock()
|
||||
defer lc.Unlock()
|
||||
|
||||
// TODO(roasbeef): logs duplicated due to breachArbiter...
|
||||
walletLog.Warnf("Unprompted commitment broadcast for ChannelPoint(%v) "+
|
||||
"detected!", lc.channelState.ChanID)
|
||||
|
||||
@ -704,7 +705,11 @@ func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEven
|
||||
obsfucator := lc.channelState.StateHintObsfucator
|
||||
broadcastStateNum := uint64(GetStateNumHint(commitTxBroadcast, obsfucator))
|
||||
|
||||
currentStateNum := lc.remoteCommitChain.tail().height
|
||||
currentStateNum, err := lc.channelState.CommitmentHeight()
|
||||
if err != nil {
|
||||
walletLog.Errorf("unable to obtain commitment height: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
// If state number spending transaction matches the current latest
|
||||
@ -713,7 +718,7 @@ func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEven
|
||||
// necessary.
|
||||
case broadcastStateNum == currentStateNum:
|
||||
walletLog.Infof("Unilateral close of ChannelPoint(%v) "+
|
||||
"detected: %v", lc.channelState.ChanID)
|
||||
"detected", lc.channelState.ChanID)
|
||||
close(lc.UnilateralCloseSignal)
|
||||
|
||||
// If the state number broadcast is lower than the remote node's
|
||||
@ -736,7 +741,7 @@ func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEven
|
||||
return
|
||||
}
|
||||
|
||||
walletLog.Infof("Punishment breach retribution created: %#v",
|
||||
walletLog.Debugf("Punishment breach retribution created: %#v",
|
||||
retribution)
|
||||
|
||||
// Finally, send the retribution struct over the contract beach
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
WitnessFactor = blockchain.WitnessScaleFactor
|
||||
WitnessFactor = blockchain.WitnessScaleFactor
|
||||
MaxTransactionWeightPolicy = blockchain.MaxBlockCost / 10
|
||||
|
||||
// The weight(cost), which is different from the !size! (see BIP-141),
|
||||
@ -21,7 +21,7 @@ const (
|
||||
// - WitnessScriptSHA256: 32 bytes
|
||||
P2WSHSize = 1 + 1 + 32
|
||||
|
||||
// P2PKH: 22 bytes
|
||||
// P2WPKH: 22 bytes
|
||||
// - OP_0: 1 byte
|
||||
// - OP_DATA: 1 byte (PublicKeyHASH160 length)
|
||||
// - PublicKeyHASH160: 20 bytes
|
||||
@ -117,7 +117,6 @@ const (
|
||||
MaxHTLCNumber = 1253
|
||||
)
|
||||
|
||||
|
||||
// estimateCommitTxCost estimate commitment transaction cost depending on the
|
||||
// precalculated cost of base transaction, witness data, which is needed for
|
||||
// paying for funding tx, and htlc cost multiplied by their count.
|
||||
@ -133,4 +132,4 @@ func estimateCommitTxCost(count int, prediction bool) int64 {
|
||||
witnessCost := int64(WitnessCommitmentTxCost)
|
||||
|
||||
return htlcCost + baseCost + witnessCost
|
||||
}
|
||||
}
|
||||
|
6
log.go
6
log.go
@ -27,6 +27,7 @@ var (
|
||||
chdbLog = btclog.Disabled
|
||||
hswcLog = btclog.Disabled
|
||||
utxnLog = btclog.Disabled
|
||||
brarLog = btclog.Disabled
|
||||
)
|
||||
|
||||
// subsystemLoggers maps each subsystem identifier to its associated logger.
|
||||
@ -41,6 +42,7 @@ var subsystemLoggers = map[string]btclog.Logger{
|
||||
"FNDG": fndgLog,
|
||||
"HSWC": hswcLog,
|
||||
"UTXN": utxnLog,
|
||||
"BRAR": brarLog,
|
||||
}
|
||||
|
||||
// useLogger updates the logger references for subsystemID to logger. Invalid
|
||||
@ -81,8 +83,12 @@ func useLogger(subsystemID string, logger btclog.Logger) {
|
||||
|
||||
case "HSWC":
|
||||
hswcLog = logger
|
||||
|
||||
case "UTXN":
|
||||
utxnLog = logger
|
||||
|
||||
case "BRAR":
|
||||
brarLog = logger
|
||||
}
|
||||
}
|
||||
|
||||
|
9
peer.go
9
peer.go
@ -866,6 +866,8 @@ func (p *peer) handleLocalClose(req *closeLinkReq) {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
p.server.breachArbiter.settledContracts <- req.chanPoint
|
||||
}()
|
||||
}
|
||||
|
||||
@ -916,6 +918,8 @@ func (p *peer) handleRemoteClose(req *lnwire.CloseRequest) {
|
||||
if err := wipeChannel(p, channel); err != nil {
|
||||
peerLog.Errorf("unable to wipe channel: %v", err)
|
||||
}
|
||||
|
||||
p.server.breachArbiter.settledContracts <- req.ChannelPoint
|
||||
}
|
||||
|
||||
// wipeChannel removes the passed channel from all indexes associated with the
|
||||
@ -960,7 +964,7 @@ func wipeChannel(p *peer, channel *lnwallet.LightningChannel) error {
|
||||
// small summary for historical records.
|
||||
if err := channel.DeleteState(); err != nil {
|
||||
peerLog.Errorf("Unable to delete ChannelPoint(%v) "+
|
||||
"from db %v", chanID, err)
|
||||
"from db: %v", chanID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1075,12 +1079,15 @@ out:
|
||||
for {
|
||||
select {
|
||||
case <-channel.UnilateralCloseSignal:
|
||||
// TODO(roasbeef): need to send HTLC outputs to nursery
|
||||
peerLog.Warnf("Remote peer has closed ChannelPoint(%v) on-chain",
|
||||
state.chanPoint)
|
||||
if err := wipeChannel(p, channel); err != nil {
|
||||
peerLog.Errorf("unable to wipe channel %v", err)
|
||||
}
|
||||
|
||||
p.server.breachArbiter.settledContracts <- state.chanPoint
|
||||
|
||||
break out
|
||||
case <-channel.ForceCloseSignal:
|
||||
peerLog.Warnf("ChannelPoint(%v) has been force "+
|
||||
|
@ -370,6 +370,8 @@ func (r *rpcServer) CloseChannel(in *lnrpc.CloseChannelRequest,
|
||||
var closeType LinkCloseType
|
||||
switch force {
|
||||
case true:
|
||||
// TODO(roasbeef): should be able to force close w/o connection
|
||||
// to peer
|
||||
closeType = CloseForce
|
||||
case false:
|
||||
closeType = CloseRegular
|
||||
|
51
server.go
51
server.go
@ -50,8 +50,9 @@ type server struct {
|
||||
fundingMgr *fundingManager
|
||||
chanDB *channeldb.DB
|
||||
|
||||
htlcSwitch *htlcSwitch
|
||||
invoices *invoiceRegistry
|
||||
htlcSwitch *htlcSwitch
|
||||
invoices *invoiceRegistry
|
||||
breachArbiter *breachArbiter
|
||||
|
||||
routingMgr *routing.RoutingManager
|
||||
|
||||
@ -88,23 +89,28 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
|
||||
|
||||
serializedPubKey := privKey.PubKey().SerializeCompressed()
|
||||
s := &server{
|
||||
lnwallet: wallet,
|
||||
bio: bio,
|
||||
chainNotifier: notifier,
|
||||
chanDB: chanDB,
|
||||
fundingMgr: newFundingManager(wallet),
|
||||
invoices: newInvoiceRegistry(chanDB),
|
||||
lnwallet: wallet,
|
||||
identityPriv: privKey,
|
||||
|
||||
invoices: newInvoiceRegistry(chanDB),
|
||||
utxoNursery: newUtxoNursery(notifier, wallet),
|
||||
|
||||
identityPriv: privKey,
|
||||
|
||||
// TODO(roasbeef): derive proper onion key based on rotation
|
||||
// schedule
|
||||
sphinx: sphinx.NewRouter(privKey, activeNetParams.Params),
|
||||
lightningID: fastsha256.Sum256(serializedPubKey),
|
||||
listeners: listeners,
|
||||
peers: make(map[int32]*peer),
|
||||
newPeers: make(chan *peer, 100),
|
||||
donePeers: make(chan *peer, 100),
|
||||
queries: make(chan interface{}),
|
||||
quit: make(chan struct{}),
|
||||
|
||||
listeners: listeners,
|
||||
|
||||
peers: make(map[int32]*peer),
|
||||
newPeers: make(chan *peer, 100),
|
||||
donePeers: make(chan *peer, 100),
|
||||
queries: make(chan interface{}),
|
||||
quit: make(chan struct{}),
|
||||
}
|
||||
|
||||
// If the debug HTLC flag is on, then we invoice a "master debug"
|
||||
@ -123,7 +129,7 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
|
||||
// the graph.
|
||||
selfVertex := serializedPubKey
|
||||
routingMgrConfig := &routing.RoutingConfig{}
|
||||
routingMgrConfig.SendMessage = func (receiver [33]byte, msg lnwire.Message) error {
|
||||
routingMgrConfig.SendMessage = func(receiver [33]byte, msg lnwire.Message) error {
|
||||
receiverID := graph.NewVertex(receiver[:])
|
||||
if receiverID == graph.NilVertex {
|
||||
peerLog.Critical("receiverID == graph.NilVertex")
|
||||
@ -135,7 +141,7 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
|
||||
nodePub := peer.addr.IdentityKey.SerializeCompressed()
|
||||
nodeVertex := graph.NewVertex(nodePub[:])
|
||||
|
||||
// We found the the target
|
||||
// We found the target
|
||||
if receiverID == nodeVertex {
|
||||
targetPeer = peer
|
||||
break
|
||||
@ -155,6 +161,13 @@ func newServer(listenAddrs []string, notifier chainntnfs.ChainNotifier,
|
||||
|
||||
s.rpcServer = newRpcServer(s)
|
||||
|
||||
s.breachArbiter = newBreachArbiter(wallet, chanDB, notifier, s.htlcSwitch)
|
||||
|
||||
s.fundingMgr = newFundingManager(wallet, s.breachArbiter)
|
||||
|
||||
// TODO(roasbeef): introduce closure and config system to decouple the
|
||||
// initialization above ^
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@ -172,11 +185,11 @@ func (s *server) Start() error {
|
||||
go s.listener(l)
|
||||
}
|
||||
|
||||
// Start the notification server. This is used so channel managment
|
||||
// Start the notification server. This is used so channel management
|
||||
// goroutines can be notified when a funding transaction reaches a
|
||||
// sufficient number of confirmations, or when the input for the
|
||||
// funding transaction is spent in an attempt at an uncooperative
|
||||
// close by the counter party.
|
||||
// funding transaction is spent in an attempt at an uncooperative close
|
||||
// by the counter party.
|
||||
if err := s.chainNotifier.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -193,6 +206,9 @@ func (s *server) Start() error {
|
||||
if err := s.utxoNursery.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.breachArbiter.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.routingMgr.Start()
|
||||
|
||||
s.wg.Add(1)
|
||||
@ -224,6 +240,7 @@ func (s *server) Stop() error {
|
||||
s.routingMgr.Stop()
|
||||
s.htlcSwitch.Stop()
|
||||
s.utxoNursery.Stop()
|
||||
s.breachArbiter.Stop()
|
||||
|
||||
s.lnwallet.Shutdown()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user