htlcswitch+lntest: modify Switch to persist resolution messages

Include unit tests for the Switch, and integration tests that
exercise the persistence logic.
This commit is contained in:
eugene committed 2022-04-29 16:40:19 -04:00
1 parent bfed7a088f
commit f7b3da4bb2
8 files changed
+518 -26

No files matched your search

+103 -11
View File
@@ -297,15 +297,23 @@ type Switch struct {
// ack in the forwarding package of the outgoing link. This was added to
// make pipelining settles more efficient.
pendingSettleFails []channeldb.SettleFailRef
// resMsgStore is used to store the set of ResolutionMsg that come from
// contractcourt. This is used so the Switch can properly forward them,
// even on restarts.
resMsgStore *resolutionStore
}
// New creates the new instance of htlc switch.
func New(cfg Config, currentHeight uint32) (*Switch, error) {
resStore := newResolutionStore(cfg.DB)
circuitMap, err := NewCircuitMap(&CircuitMapConfig{
DB: cfg.DB,
FetchAllOpenChannels: cfg.FetchAllOpenChannels,
FetchClosedChannels: cfg.FetchClosedChannels,
ExtractErrorEncrypter: cfg.ExtractErrorEncrypter,
CheckResolutionMsg: resStore.checkResolutionMsg,
})
if err != nil {
return nil, err
@@ -323,6 +331,7 @@ func New(cfg Config, currentHeight uint32) (*Switch, error) {
htlcPlex: make(chan *plexPacket),
chanCloseRequests: make(chan *ChanClose),
resolutionMsgs: make(chan *resolutionMsg),
resMsgStore: resStore,
quit: make(chan struct{}),
}
@@ -342,7 +351,7 @@ func New(cfg Config, currentHeight uint32) (*Switch, error) {
type resolutionMsg struct {
contractcourt.ResolutionMsg
doneChan chan struct{}
errChan chan error
}
// ProcessContractResolution is called by active contract resolvers once a
@@ -351,25 +360,23 @@ type resolutionMsg struct {
// 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{})
errChan := make(chan error, 1)
select {
case s.resolutionMsgs <- &resolutionMsg{
ResolutionMsg: msg,
doneChan: done,
errChan: errChan,
}:
case <-s.quit:
return ErrSwitchExiting
}
select {
case <-done:
case err := <-errChan:
return err
case <-s.quit:
return ErrSwitchExiting
}
return nil
}
// GetPaymentResult returns the the result of the payment attempt with the
@@ -1678,6 +1685,28 @@ out:
go s.cfg.LocalChannelClose(peerPub[:], req)
case resolutionMsg := <-s.resolutionMsgs:
// We'll persist the resolution message to the Switch's
// resolution store.
resMsg := resolutionMsg.ResolutionMsg
err := s.resMsgStore.addResolutionMsg(&resMsg)
if err != nil {
// This will only fail if there is a database
// error or a serialization error. Sending the
// error prevents the contractcourt from being
// in a state where it believes the send was
// successful, when it wasn't.
log.Errorf("unable to add resolution msg: %v",
err)
resolutionMsg.errChan <- err
continue
}
// At this point, the resolution message has been
// persisted. It is safe to signal success by sending
// a nil error since the Switch will re-deliver the
// resolution message on restart.
resolutionMsg.errChan <- nil
pkt := &htlcPacket{
outgoingChanID: resolutionMsg.SourceChan,
outgoingHTLCID: resolutionMsg.HtlcIndex,
@@ -1703,14 +1732,11 @@ out:
// 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)
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.
@@ -1863,6 +1889,72 @@ func (s *Switch) Start() error {
return err
}
if err := s.reforwardResolutions(); err != nil {
// We are already stopping so we can ignore the error.
_ = s.Stop()
log.Errorf("unable to reforward resolutions: %v", err)
return err
}
return nil
}
// reforwardResolutions fetches the set of resolution messages stored on-disk
// and reforwards them if their circuits are still open. If the circuits have
// been deleted, then we will delete the resolution message from the database.
func (s *Switch) reforwardResolutions() error {
// Fetch all stored resolution messages, deleting the ones that are
// resolved.
resMsgs, err := s.resMsgStore.fetchAllResolutionMsg()
if err != nil {
return err
}
switchPackets := make([]*htlcPacket, 0, len(resMsgs))
for _, resMsg := range resMsgs {
// If the open circuit no longer exists, then we can remove the
// message from the store.
outKey := CircuitKey{
ChanID: resMsg.SourceChan,
HtlcID: resMsg.HtlcIndex,
}
if s.circuits.LookupOpenCircuit(outKey) == nil {
// The open circuit doesn't exist.
err := s.resMsgStore.deleteResolutionMsg(&outKey)
if err != nil {
return err
}
continue
}
// The circuit is still open, so we can assume that the link or
// switch (if we are the source) hasn't cleaned it up yet.
resPkt := &htlcPacket{
outgoingChanID: resMsg.SourceChan,
outgoingHTLCID: resMsg.HtlcIndex,
isResolution: true,
}
if resMsg.Failure != nil {
resPkt.htlc = &lnwire.UpdateFailHTLC{}
} else {
resPkt.htlc = &lnwire.UpdateFulfillHTLC{
PaymentPreimage: *resMsg.PreImage,
}
}
switchPackets = append(switchPackets, resPkt)
}
// We'll now dispatch the set of resolution messages to the proper
// destination. An error is only encountered here if the switch is
// shutting down.
if err := s.ForwardPackets(nil, switchPackets...); err != nil {
return err
}
return nil
}