github.com/decred/dcrlnd@v0.7.6/htlcswitch/interceptable_switch.go (about) 1 package htlcswitch 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/decred/dcrlnd/channeldb" 8 "github.com/decred/dcrlnd/htlcswitch/hop" 9 "github.com/decred/dcrlnd/lntypes" 10 "github.com/decred/dcrlnd/lnwire" 11 "github.com/go-errors/errors" 12 ) 13 14 var ( 15 // ErrFwdNotExists is an error returned when the caller tries to resolve 16 // a forward that doesn't exist anymore. 17 ErrFwdNotExists = errors.New("forward does not exist") 18 ) 19 20 // InterceptableSwitch is an implementation of ForwardingSwitch interface. 21 // This implementation is used like a proxy that wraps the switch and 22 // intercepts forward requests. A reference to the Switch is held in order 23 // to communicate back the interception result where the options are: 24 // Resume - forwards the original request to the switch as is. 25 // Settle - routes UpdateFulfillHTLC to the originating link. 26 // Fail - routes UpdateFailHTLC to the originating link. 27 type InterceptableSwitch struct { 28 sync.RWMutex 29 30 // htlcSwitch is the underline switch 31 htlcSwitch *Switch 32 33 // fwdInterceptor is the callback that is called for each forward of 34 // an incoming htlc. It should return true if it is interested in handling 35 // it. 36 fwdInterceptor ForwardInterceptor 37 } 38 39 // NewInterceptableSwitch returns an instance of InterceptableSwitch. 40 func NewInterceptableSwitch(s *Switch) *InterceptableSwitch { 41 return &InterceptableSwitch{htlcSwitch: s} 42 } 43 44 // SetInterceptor sets the ForwardInterceptor to be used. 45 func (s *InterceptableSwitch) SetInterceptor( 46 interceptor ForwardInterceptor) { 47 48 s.Lock() 49 defer s.Unlock() 50 s.fwdInterceptor = interceptor 51 } 52 53 // ForwardPackets attempts to forward the batch of htlcs through the 54 // switch, any failed packets will be returned to the provided 55 // ChannelLink. The link's quit signal should be provided to allow 56 // cancellation of forwarding during link shutdown. 57 func (s *InterceptableSwitch) ForwardPackets(linkQuit chan struct{}, 58 packets ...*htlcPacket) error { 59 60 var interceptor ForwardInterceptor 61 s.Lock() 62 interceptor = s.fwdInterceptor 63 s.Unlock() 64 65 // Optimize for the case we don't have an interceptor. 66 if interceptor == nil { 67 return s.htlcSwitch.ForwardPackets(linkQuit, packets...) 68 } 69 70 var notIntercepted []*htlcPacket 71 for _, p := range packets { 72 if !s.interceptForward(p, interceptor, linkQuit) { 73 notIntercepted = append(notIntercepted, p) 74 } 75 } 76 return s.htlcSwitch.ForwardPackets(linkQuit, notIntercepted...) 77 } 78 79 // interceptForward checks if there is any external interceptor interested in 80 // this packet. Currently only htlc type of UpdateAddHTLC that are forwarded 81 // are being checked for interception. It can be extended in the future given 82 // the right use case. 83 func (s *InterceptableSwitch) interceptForward(packet *htlcPacket, 84 interceptor ForwardInterceptor, linkQuit chan struct{}) bool { 85 86 switch htlc := packet.htlc.(type) { 87 case *lnwire.UpdateAddHTLC: 88 // We are not interested in intercepting initated payments. 89 if packet.incomingChanID == hop.Source { 90 return false 91 } 92 93 intercepted := &interceptedForward{ 94 linkQuit: linkQuit, 95 htlc: htlc, 96 packet: packet, 97 htlcSwitch: s.htlcSwitch, 98 } 99 100 // If this htlc was intercepted, don't handle the forward. 101 return interceptor(intercepted) 102 default: 103 return false 104 } 105 } 106 107 // interceptedForward implements the InterceptedForward interface. 108 // It is passed from the switch to external interceptors that are interested 109 // in holding forwards and resolve them manually. 110 type interceptedForward struct { 111 linkQuit chan struct{} 112 htlc *lnwire.UpdateAddHTLC 113 packet *htlcPacket 114 htlcSwitch *Switch 115 } 116 117 // Packet returns the intercepted htlc packet. 118 func (f *interceptedForward) Packet() InterceptedPacket { 119 return InterceptedPacket{ 120 IncomingCircuit: channeldb.CircuitKey{ 121 ChanID: f.packet.incomingChanID, 122 HtlcID: f.packet.incomingHTLCID, 123 }, 124 OutgoingChanID: f.packet.outgoingChanID, 125 Hash: f.htlc.PaymentHash, 126 OutgoingExpiry: f.htlc.Expiry, 127 OutgoingAmount: f.htlc.Amount, 128 IncomingAmount: f.packet.incomingAmount, 129 IncomingExpiry: f.packet.incomingTimeout, 130 CustomRecords: f.packet.customRecords, 131 OnionBlob: f.htlc.OnionBlob, 132 } 133 } 134 135 // Resume resumes the default behavior as if the packet was not intercepted. 136 func (f *interceptedForward) Resume() error { 137 return f.htlcSwitch.ForwardPackets(f.linkQuit, f.packet) 138 } 139 140 // Fail forward a failed packet to the switch. 141 func (f *interceptedForward) Fail() error { 142 update, err := f.htlcSwitch.cfg.FetchLastChannelUpdate( 143 f.packet.incomingChanID, 144 ) 145 if err != nil { 146 return err 147 } 148 149 reason, err := f.packet.obfuscator.EncryptFirstHop( 150 lnwire.NewTemporaryChannelFailure(update), 151 ) 152 if err != nil { 153 return fmt.Errorf("failed to encrypt failure reason %v", err) 154 } 155 return f.resolve(&lnwire.UpdateFailHTLC{ 156 Reason: reason, 157 }) 158 } 159 160 // Settle forwards a settled packet to the switch. 161 func (f *interceptedForward) Settle(preimage lntypes.Preimage) error { 162 if !preimage.Matches(f.htlc.PaymentHash) { 163 return errors.New("preimage does not match hash") 164 } 165 return f.resolve(&lnwire.UpdateFulfillHTLC{ 166 PaymentPreimage: preimage, 167 }) 168 } 169 170 // resolve is used for both Settle and Fail and forwards the message to the 171 // switch. 172 func (f *interceptedForward) resolve(message lnwire.Message) error { 173 pkt := &htlcPacket{ 174 incomingChanID: f.packet.incomingChanID, 175 incomingHTLCID: f.packet.incomingHTLCID, 176 outgoingChanID: f.packet.outgoingChanID, 177 outgoingHTLCID: f.packet.outgoingHTLCID, 178 isResolution: true, 179 circuit: f.packet.circuit, 180 htlc: message, 181 obfuscator: f.packet.obfuscator, 182 sourceRef: f.packet.sourceRef, 183 } 184 return f.htlcSwitch.mailOrchestrator.Deliver(pkt.incomingChanID, pkt) 185 }