diff --git a/breacharbiter.go b/breacharbiter.go new file mode 100644 index 00000000..bbbd9041 --- /dev/null +++ b/breacharbiter.go @@ -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 +} diff --git a/fundingmanager.go b/fundingmanager.go index 55669229..80fe9297 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -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 } diff --git a/htlcswitch.go b/htlcswitch.go index 68329bb1..067deb97 100644 --- a/htlcswitch.go +++ b/htlcswitch.go @@ -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 diff --git a/lnd_test.go b/lnd_test.go index d90829e6..291e0aa7 100644 --- a/lnd_test.go +++ b/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 diff --git a/lnwallet/channel.go b/lnwallet/channel.go index e4e970b9..82458848 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -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 diff --git a/lnwallet/size.go b/lnwallet/size.go index 1c13bd2f..20f0a20e 100644 --- a/lnwallet/size.go +++ b/lnwallet/size.go @@ -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 -} \ No newline at end of file +} diff --git a/log.go b/log.go index e4c265cf..86beb0ab 100644 --- a/log.go +++ b/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 } } diff --git a/peer.go b/peer.go index fb8f50ac..3af48e92 100644 --- a/peer.go +++ b/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 "+ diff --git a/rpcserver.go b/rpcserver.go index fb05087a..d83f627a 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -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 diff --git a/server.go b/server.go index ce853bcc..898e87a7 100644 --- a/server.go +++ b/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()