htlcswitch: add new ProcessContractResolution method

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.
This commit is contained in:
Olaoluwa Osuntokun 2018-01-16 20:11:01 -08:00
parent 8807d1d752
commit 9cb3657314
No known key found for this signature in database
GPG Key ID: 964EA263DD637C21
3 changed files with 110 additions and 5 deletions

@ -79,7 +79,7 @@ const (
// wireCourier is a type of courier that handles wire messages. // wireCourier is a type of courier that handles wire messages.
wireCourier courierType = iota wireCourier courierType = iota
// pktCourier is a type of courier that handles hltc packets. // pktCourier is a type of courier that handles htlc packets.
pktCourier pktCourier
) )

@ -46,4 +46,11 @@ type htlcPacket struct {
// of a forwarded fail packet are already set and do not need to be looked // of a forwarded fail packet are already set and do not need to be looked
// up in the circuit map. // up in the circuit map.
isRouted bool 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
} }

@ -13,6 +13,7 @@ import (
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@ -161,6 +162,10 @@ type Switch struct {
// the channel close handler. // the channel close handler.
chanCloseRequests chan *ChanClose 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 // linkControl is a channel used to propagate add/remove/get htlc
// switch handler commands. // switch handler commands.
linkControl chan interface{} linkControl chan interface{}
@ -177,11 +182,48 @@ func New(cfg Config) *Switch {
pendingPayments: make(map[uint64]*pendingPayment), pendingPayments: make(map[uint64]*pendingPayment),
htlcPlex: make(chan *plexPacket), htlcPlex: make(chan *plexPacket),
chanCloseRequests: make(chan *ChanClose), chanCloseRequests: make(chan *ChanClose),
resolutionMsgs: make(chan *resolutionMsg),
linkControl: make(chan interface{}), linkControl: make(chan interface{}),
quit: make(chan struct{}), 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 // SendHTLC is used by other subsystems which aren't belong to htlc switch
// package in order to send the htlc update. // package in order to send the htlc update.
func (s *Switch) SendHTLC(nextNode [33]byte, htlc *lnwire.UpdateAddHTLC, 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.incomingChanID = circuit.IncomingChanID
packet.incomingHTLCID = circuit.IncomingHTLCID packet.incomingHTLCID = circuit.IncomingHTLCID
// Obfuscate the error message for fail updates before sending back // Obfuscate the error message for fail updates before
// through the circuit unless the payment was generated locally. // sending back through the circuit unless the payment
// was generated locally.
if circuit.ErrorEncrypter != nil { if circuit.ErrorEncrypter != nil {
if htlc, ok := htlc.(*lnwire.UpdateFailHTLC); ok { if htlc, ok := htlc.(*lnwire.UpdateFailHTLC); ok {
// 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 = circuit.ErrorEncrypter.IntermediateEncrypt(
htlc.Reason) htlc.Reason,
)
}
} }
} }
} }
@ -736,6 +800,40 @@ func (s *Switch) htlcForwarder() {
go s.cfg.LocalChannelClose(peerPub[:], req) 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 // A new packet has arrived for forwarding, we'll interpret the
// packet concretely, then either forward it along, or // packet concretely, then either forward it along, or
// interpret a return packet to a locally initialized one. // interpret a return packet to a locally initialized one.