github.com/decred/dcrlnd@v0.7.6/routing/control_tower.go (about) 1 package routing 2 3 import ( 4 "sync" 5 6 "github.com/decred/dcrlnd/channeldb" 7 "github.com/decred/dcrlnd/lntypes" 8 "github.com/decred/dcrlnd/multimutex" 9 "github.com/decred/dcrlnd/queue" 10 ) 11 12 // ControlTower tracks all outgoing payments made, whose primary purpose is to 13 // prevent duplicate payments to the same payment hash. In production, a 14 // persistent implementation is preferred so that tracking can survive across 15 // restarts. Payments are transitioned through various payment states, and the 16 // ControlTower interface provides access to driving the state transitions. 17 type ControlTower interface { 18 // This method checks that no suceeded payment exist for this payment 19 // hash. 20 InitPayment(lntypes.Hash, *channeldb.PaymentCreationInfo) error 21 22 // RegisterAttempt atomically records the provided HTLCAttemptInfo. 23 RegisterAttempt(lntypes.Hash, *channeldb.HTLCAttemptInfo) error 24 25 // SettleAttempt marks the given attempt settled with the preimage. If 26 // this is a multi shard payment, this might implicitly mean the the 27 // full payment succeeded. 28 // 29 // After invoking this method, InitPayment should always return an 30 // error to prevent us from making duplicate payments to the same 31 // payment hash. The provided preimage is atomically saved to the DB 32 // for record keeping. 33 SettleAttempt(lntypes.Hash, uint64, *channeldb.HTLCSettleInfo) ( 34 *channeldb.HTLCAttempt, error) 35 36 // FailAttempt marks the given payment attempt failed. 37 FailAttempt(lntypes.Hash, uint64, *channeldb.HTLCFailInfo) ( 38 *channeldb.HTLCAttempt, error) 39 40 // FetchPayment fetches the payment corresponding to the given payment 41 // hash. 42 FetchPayment(paymentHash lntypes.Hash) (*channeldb.MPPayment, error) 43 44 // Fail transitions a payment into the Failed state, and records the 45 // ultimate reason the payment failed. Note that this should only be 46 // called when all active active attempts are already failed. After 47 // invoking this method, InitPayment should return nil on its next call 48 // for this payment hash, allowing the user to make a subsequent 49 // payment. 50 Fail(lntypes.Hash, channeldb.FailureReason) error 51 52 // FetchInFlightPayments returns all payments with status InFlight. 53 FetchInFlightPayments() ([]*channeldb.MPPayment, error) 54 55 // SubscribePayment subscribes to updates for the payment with the given 56 // hash. A first update with the current state of the payment is always 57 // sent out immediately. 58 SubscribePayment(paymentHash lntypes.Hash) (*ControlTowerSubscriber, 59 error) 60 } 61 62 // ControlTowerSubscriber contains the state for a payment update subscriber. 63 type ControlTowerSubscriber struct { 64 // Updates is the channel over which *channeldb.MPPayment updates can be 65 // received. 66 Updates <-chan interface{} 67 68 queue *queue.ConcurrentQueue 69 quit chan struct{} 70 } 71 72 // newControlTowerSubscriber instantiates a new subscriber state object. 73 func newControlTowerSubscriber() *ControlTowerSubscriber { 74 // Create a queue for payment updates. 75 queue := queue.NewConcurrentQueue(20) 76 queue.Start() 77 78 return &ControlTowerSubscriber{ 79 Updates: queue.ChanOut(), 80 queue: queue, 81 quit: make(chan struct{}), 82 } 83 } 84 85 // Close signals that the subscriber is no longer interested in updates. 86 func (s *ControlTowerSubscriber) Close() { 87 // Close quit channel so that any pending writes to the queue are 88 // cancelled. 89 close(s.quit) 90 91 // Stop the queue goroutine so that it won't leak. 92 s.queue.Stop() 93 } 94 95 // controlTower is persistent implementation of ControlTower to restrict 96 // double payment sending. 97 type controlTower struct { 98 db *channeldb.PaymentControl 99 100 subscribers map[lntypes.Hash][]*ControlTowerSubscriber 101 subscribersMtx sync.Mutex 102 103 // paymentsMtx provides synchronization on the payment level to ensure 104 // that no race conditions occur in between updating the database and 105 // sending a notification. 106 paymentsMtx *multimutex.HashMutex 107 } 108 109 // NewControlTower creates a new instance of the controlTower. 110 func NewControlTower(db *channeldb.PaymentControl) ControlTower { 111 return &controlTower{ 112 db: db, 113 subscribers: make(map[lntypes.Hash][]*ControlTowerSubscriber), 114 paymentsMtx: multimutex.NewHashMutex(), 115 } 116 } 117 118 // InitPayment checks or records the given PaymentCreationInfo with the DB, 119 // making sure it does not already exist as an in-flight payment. Then this 120 // method returns successfully, the payment is guranteeed to be in the InFlight 121 // state. 122 func (p *controlTower) InitPayment(paymentHash lntypes.Hash, 123 info *channeldb.PaymentCreationInfo) error { 124 125 return p.db.InitPayment(paymentHash, info) 126 } 127 128 // RegisterAttempt atomically records the provided HTLCAttemptInfo to the 129 // DB. 130 func (p *controlTower) RegisterAttempt(paymentHash lntypes.Hash, 131 attempt *channeldb.HTLCAttemptInfo) error { 132 133 p.paymentsMtx.Lock(paymentHash) 134 defer p.paymentsMtx.Unlock(paymentHash) 135 136 payment, err := p.db.RegisterAttempt(paymentHash, attempt) 137 if err != nil { 138 return err 139 } 140 141 // Notify subscribers of the attempt registration. 142 p.notifySubscribers(paymentHash, payment) 143 144 return nil 145 } 146 147 // SettleAttempt marks the given attempt settled with the preimage. If 148 // this is a multi shard payment, this might implicitly mean the the 149 // full payment succeeded. 150 func (p *controlTower) SettleAttempt(paymentHash lntypes.Hash, 151 attemptID uint64, settleInfo *channeldb.HTLCSettleInfo) ( 152 *channeldb.HTLCAttempt, error) { 153 154 p.paymentsMtx.Lock(paymentHash) 155 defer p.paymentsMtx.Unlock(paymentHash) 156 157 payment, err := p.db.SettleAttempt(paymentHash, attemptID, settleInfo) 158 if err != nil { 159 return nil, err 160 } 161 162 // Notify subscribers of success event. 163 p.notifySubscribers(paymentHash, payment) 164 165 return payment.GetAttempt(attemptID) 166 } 167 168 // FailAttempt marks the given payment attempt failed. 169 func (p *controlTower) FailAttempt(paymentHash lntypes.Hash, 170 attemptID uint64, failInfo *channeldb.HTLCFailInfo) ( 171 *channeldb.HTLCAttempt, error) { 172 173 p.paymentsMtx.Lock(paymentHash) 174 defer p.paymentsMtx.Unlock(paymentHash) 175 176 payment, err := p.db.FailAttempt(paymentHash, attemptID, failInfo) 177 if err != nil { 178 return nil, err 179 } 180 181 // Notify subscribers of failed attempt. 182 p.notifySubscribers(paymentHash, payment) 183 184 return payment.GetAttempt(attemptID) 185 } 186 187 // FetchPayment fetches the payment corresponding to the given payment hash. 188 func (p *controlTower) FetchPayment(paymentHash lntypes.Hash) ( 189 *channeldb.MPPayment, error) { 190 191 return p.db.FetchPayment(paymentHash) 192 } 193 194 // Fail transitions a payment into the Failed state, and records the reason the 195 // payment failed. After invoking this method, InitPayment should return nil on 196 // its next call for this payment hash, allowing the switch to make a 197 // subsequent payment. 198 func (p *controlTower) Fail(paymentHash lntypes.Hash, 199 reason channeldb.FailureReason) error { 200 201 p.paymentsMtx.Lock(paymentHash) 202 defer p.paymentsMtx.Unlock(paymentHash) 203 204 payment, err := p.db.Fail(paymentHash, reason) 205 if err != nil { 206 return err 207 } 208 209 // Notify subscribers of fail event. 210 p.notifySubscribers(paymentHash, payment) 211 212 return nil 213 } 214 215 // FetchInFlightPayments returns all payments with status InFlight. 216 func (p *controlTower) FetchInFlightPayments() ([]*channeldb.MPPayment, error) { 217 return p.db.FetchInFlightPayments() 218 } 219 220 // SubscribePayment subscribes to updates for the payment with the given hash. A 221 // first update with the current state of the payment is always sent out 222 // immediately. 223 func (p *controlTower) SubscribePayment(paymentHash lntypes.Hash) ( 224 *ControlTowerSubscriber, error) { 225 226 // Take lock before querying the db to prevent missing or duplicating an 227 // update. 228 p.paymentsMtx.Lock(paymentHash) 229 defer p.paymentsMtx.Unlock(paymentHash) 230 231 payment, err := p.db.FetchPayment(paymentHash) 232 if err != nil { 233 return nil, err 234 } 235 236 subscriber := newControlTowerSubscriber() 237 238 // Always write current payment state to the channel. 239 subscriber.queue.ChanIn() <- payment 240 241 // Payment is currently in flight. Register this subscriber for further 242 // updates. Otherwise this update is the final update and the incoming 243 // channel can be closed. This will close the queue's outgoing channel 244 // when all updates have been written. 245 if payment.Status == channeldb.StatusInFlight { 246 p.subscribersMtx.Lock() 247 p.subscribers[paymentHash] = append( 248 p.subscribers[paymentHash], subscriber, 249 ) 250 p.subscribersMtx.Unlock() 251 } else { 252 close(subscriber.queue.ChanIn()) 253 } 254 255 return subscriber, nil 256 } 257 258 // notifySubscribers sends a final payment event to all subscribers of this 259 // payment. The channel will be closed after this. Note that this function must 260 // be executed atomically (by means of a lock) with the database update to 261 // guarantuee consistency of the notifications. 262 func (p *controlTower) notifySubscribers(paymentHash lntypes.Hash, 263 event *channeldb.MPPayment) { 264 265 // Get all subscribers for this payment. 266 p.subscribersMtx.Lock() 267 list, ok := p.subscribers[paymentHash] 268 if !ok { 269 p.subscribersMtx.Unlock() 270 return 271 } 272 273 // If the payment reached a terminal state, the subscriber list can be 274 // cleared. There won't be any more updates. 275 terminal := event.Status != channeldb.StatusInFlight 276 if terminal { 277 delete(p.subscribers, paymentHash) 278 } 279 p.subscribersMtx.Unlock() 280 281 // Notify all subscribers of the event. 282 for _, subscriber := range list { 283 select { 284 case subscriber.queue.ChanIn() <- event: 285 // If this event is the last, close the incoming channel 286 // of the queue. This will signal the subscriber that 287 // there won't be any more updates. 288 if terminal { 289 close(subscriber.queue.ChanIn()) 290 } 291 292 // If subscriber disappeared, skip notification. For further 293 // notifications, we'll keep skipping over this subscriber. 294 case <-subscriber.quit: 295 } 296 } 297 }