From 9cb36573148ec0658e0f7899e72311a0f4cdf057 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 16 Jan 2018 20:11:01 -0800 Subject: [PATCH] htlcswitch: add new ProcessContractResolution method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In this commit, we add a new method: ProcessContractResolution. This will be used by entities of the contract court package to notify us whenever they discover that we can resolve an incoming contract off-chain after the outgoing contract was fully resolved on-chain. We’ll take a contractcourt.ResolutionMsg and map it to the proper internal package so we can fully resolve an active circuit. --- htlcswitch/mailbox.go | 2 +- htlcswitch/packet.go | 7 +++ htlcswitch/switch.go | 106 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/htlcswitch/mailbox.go b/htlcswitch/mailbox.go index 309e79d7..9cbd07ad 100644 --- a/htlcswitch/mailbox.go +++ b/htlcswitch/mailbox.go @@ -79,7 +79,7 @@ const ( // wireCourier is a type of courier that handles wire messages. wireCourier courierType = iota - // pktCourier is a type of courier that handles hltc packets. + // pktCourier is a type of courier that handles htlc packets. pktCourier ) diff --git a/htlcswitch/packet.go b/htlcswitch/packet.go index 3038f94e..262826b0 100644 --- a/htlcswitch/packet.go +++ b/htlcswitch/packet.go @@ -46,4 +46,11 @@ type htlcPacket struct { // of a forwarded fail packet are already set and do not need to be looked // up in the circuit map. isRouted bool + + // isResolution is set to true if this packet was actually an incoming + // resolution message from an outside sub-system. We'll treat these as + // if they emanated directly from the switch. As a result, we'll + // encrypt all errors related to this packet as if we were the first + // hop. + isResolution bool } diff --git a/htlcswitch/switch.go b/htlcswitch/switch.go index f98d98c1..c67c31a1 100644 --- a/htlcswitch/switch.go +++ b/htlcswitch/switch.go @@ -13,6 +13,7 @@ import ( "github.com/roasbeef/btcd/btcec" "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/contractcourt" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" @@ -161,6 +162,10 @@ type Switch struct { // the channel close handler. chanCloseRequests chan *ChanClose + // resolutionMsgs is the channel that all external contract resolution + // messages will be sent over. + resolutionMsgs chan *resolutionMsg + // linkControl is a channel used to propagate add/remove/get htlc // switch handler commands. linkControl chan interface{} @@ -177,11 +182,48 @@ func New(cfg Config) *Switch { pendingPayments: make(map[uint64]*pendingPayment), htlcPlex: make(chan *plexPacket), chanCloseRequests: make(chan *ChanClose), + resolutionMsgs: make(chan *resolutionMsg), linkControl: make(chan interface{}), quit: make(chan struct{}), } } +// resolutionMsg is a struct that wraps an existing ResolutionMsg with a done +// channel. We'll use this channel to synchronize delivery of the message with +// the caller. +type resolutionMsg struct { + contractcourt.ResolutionMsg + + doneChan chan struct{} +} + +// ProcessContractResolution is called by active contract resolvers once a +// contract they are watching over has been fully resolved. The message carries +// an external signal that *would* have been sent if the outgoing channel +// didn't need to go to the chain in order to fulfill a contract. We'll process +// this message just as if it came from an active outgoing channel. +func (s *Switch) ProcessContractResolution(msg contractcourt.ResolutionMsg) error { + + done := make(chan struct{}) + + select { + case s.resolutionMsgs <- &resolutionMsg{ + ResolutionMsg: msg, + doneChan: done, + }: + case <-s.quit: + return fmt.Errorf("switch shutting down") + } + + select { + case <-done: + case <-s.quit: + return fmt.Errorf("switch shutting down") + } + + return nil +} + // SendHTLC is used by other subsystems which aren't belong to htlc switch // package in order to send the htlc update. func (s *Switch) SendHTLC(nextNode [33]byte, htlc *lnwire.UpdateAddHTLC, @@ -620,12 +662,34 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error { packet.incomingChanID = circuit.IncomingChanID packet.incomingHTLCID = circuit.IncomingHTLCID - // Obfuscate the error message for fail updates before sending back - // through the circuit unless the payment was generated locally. + // Obfuscate the error message for fail updates before + // sending back through the circuit unless the payment + // was generated locally. if circuit.ErrorEncrypter != nil { if htlc, ok := htlc.(*lnwire.UpdateFailHTLC); ok { - htlc.Reason = circuit.ErrorEncrypter.IntermediateEncrypt( - htlc.Reason) + // If this is a resolution message, + // then we'll need to encrypt it as + // it's actually internally sourced. + if packet.isResolution { + // TODO(roasbeef): don't need to pass actually? + failure := &lnwire.FailPermanentChannelFailure{} + htlc.Reason, err = circuit.ErrorEncrypter.EncryptFirstHop( + failure, + ) + if err != nil { + err := errors.Errorf("unable to obfuscate "+ + "error: %v", err) + log.Error(err) + } + } else { + // Otherwise, it's a forwarded + // error, so we'll perform a + // wrapper encryption as + // normal. + htlc.Reason = circuit.ErrorEncrypter.IntermediateEncrypt( + htlc.Reason, + ) + } } } } @@ -736,6 +800,40 @@ func (s *Switch) htlcForwarder() { go s.cfg.LocalChannelClose(peerPub[:], req) + case resolutionMsg := <-s.resolutionMsgs: + pkt := &htlcPacket{ + outgoingChanID: resolutionMsg.SourceChan, + outgoingHTLCID: resolutionMsg.HtlcIndex, + isResolution: true, + } + + // Resolution messages will either be cancelling + // backwards an existing HTLC, or settling a previously + // outgoing HTLC. Based on this, we'll map the message + // to the proper htlcPacket. + if resolutionMsg.Failure != nil { + pkt.htlc = &lnwire.UpdateFailHTLC{} + } else { + pkt.htlc = &lnwire.UpdateFufillHTLC{ + PaymentPreimage: *resolutionMsg.PreImage, + } + } + + log.Infof("Received outside contract resolution, "+ + "mapping to: %v", spew.Sdump(pkt)) + + // We don't check the error, as the only failure we can + // encounter is due to the circuit already being + // closed. This is fine, as processing this message is + // meant to be idempotent. + err := s.handlePacketForward(pkt) + if err != nil { + log.Errorf("Unable to forward resolution msg: %v", err) + } + + // With the message processed, we'll now close out + close(resolutionMsg.doneChan) + // A new packet has arrived for forwarding, we'll interpret the // packet concretely, then either forward it along, or // interpret a return packet to a locally initialized one.