github.com/decred/dcrlnd@v0.7.6/routing/missioncontrol_store.go (about) 1 package routing 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "fmt" 7 "sync" 8 "time" 9 10 list "github.com/bahlo/generic-list-go" 11 "github.com/decred/dcrd/wire" 12 "github.com/decred/dcrlnd/channeldb" 13 "github.com/decred/dcrlnd/kvdb" 14 "github.com/decred/dcrlnd/lnwire" 15 ) 16 17 var ( 18 // resultsKey is the fixed key under which the attempt results are 19 // stored. 20 resultsKey = []byte("missioncontrol-results") 21 22 // Big endian is the preferred byte order, due to cursor scans over 23 // integer keys iterating in order. 24 byteOrder = binary.BigEndian 25 ) 26 27 const ( 28 // unknownFailureSourceIdx is the database encoding of an unknown error 29 // source. 30 unknownFailureSourceIdx = -1 31 ) 32 33 // missionControlStore is a bbolt db based implementation of a mission control 34 // store. It stores the raw payment attempt data from which the internal mission 35 // controls state can be rederived on startup. This allows the mission control 36 // internal data structure to be changed without requiring a database migration. 37 // Also changes to mission control parameters can be applied to historical data. 38 // Finally, it enables importing raw data from an external source. 39 type missionControlStore struct { 40 done chan struct{} 41 wg sync.WaitGroup 42 db kvdb.Backend 43 queueMx sync.Mutex 44 45 // queue stores all pending payment results not yet added to the store. 46 queue *list.List[*paymentResult] 47 48 // queueChan is signalled when the first item is put into queue after 49 // a storeResult(). 50 queueChan chan struct{} 51 52 // keys holds the stored MC store item keys in the order of storage. 53 // We use this list when adding/deleting items from the database to 54 // avoid cursor use which may be slow in the remote DB case. 55 keys *list.List[string] 56 57 // keysMap holds the stored MC store item keys. We use this map to check 58 // if a new payment result has already been stored. 59 keysMap map[string]struct{} 60 61 // maxRecords is the maximum amount of records we will store in the db. 62 maxRecords int 63 64 // flushInterval is the configured interval we use to store new results 65 // and delete outdated ones from the db. 66 flushInterval time.Duration 67 } 68 69 func newMissionControlStore(db kvdb.Backend, maxRecords int, 70 flushInterval time.Duration) (*missionControlStore, error) { 71 72 var ( 73 keys *list.List[string] 74 keysMap map[string]struct{} 75 ) 76 77 // Create buckets if not yet existing. 78 err := kvdb.Update(db, func(tx kvdb.RwTx) error { 79 resultsBucket, err := tx.CreateTopLevelBucket(resultsKey) 80 if err != nil { 81 return fmt.Errorf("cannot create results bucket: %v", 82 err) 83 } 84 85 // Collect all keys to be able to quickly calculate the 86 // difference when updating the DB state. 87 c := resultsBucket.ReadCursor() 88 for k, _ := c.First(); k != nil; k, _ = c.Next() { 89 keys.PushBack(string(k)) 90 keysMap[string(k)] = struct{}{} 91 } 92 93 return nil 94 }, func() { 95 keys = list.New[string]() 96 keysMap = make(map[string]struct{}) 97 }) 98 if err != nil { 99 return nil, err 100 } 101 102 log.Infof("Loaded %d mission control entries", len(keysMap)) 103 104 return &missionControlStore{ 105 done: make(chan struct{}), 106 db: db, 107 queue: list.New[*paymentResult](), 108 queueChan: make(chan struct{}, 1), 109 keys: keys, 110 keysMap: keysMap, 111 maxRecords: maxRecords, 112 flushInterval: flushInterval, 113 }, nil 114 } 115 116 // clear removes all results from the db. 117 func (b *missionControlStore) clear() error { 118 b.queueMx.Lock() 119 defer b.queueMx.Unlock() 120 121 err := kvdb.Update(b.db, func(tx kvdb.RwTx) error { 122 if err := tx.DeleteTopLevelBucket(resultsKey); err != nil { 123 return err 124 } 125 126 _, err := tx.CreateTopLevelBucket(resultsKey) 127 return err 128 }, func() {}) 129 130 if err != nil { 131 return err 132 } 133 134 b.queue = list.New[*paymentResult]() 135 return nil 136 } 137 138 // fetchAll returns all results currently stored in the database. 139 func (b *missionControlStore) fetchAll() ([]*paymentResult, error) { 140 var results []*paymentResult 141 142 err := kvdb.View(b.db, func(tx kvdb.RTx) error { 143 resultBucket := tx.ReadBucket(resultsKey) 144 results = make([]*paymentResult, 0) 145 146 return resultBucket.ForEach(func(k, v []byte) error { 147 result, err := deserializeResult(k, v) 148 if err != nil { 149 return err 150 } 151 152 results = append(results, result) 153 154 return nil 155 }) 156 157 }, func() { 158 results = nil 159 }) 160 if err != nil { 161 return nil, err 162 } 163 164 return results, nil 165 } 166 167 // serializeResult serializes a payment result and returns a key and value byte 168 // slice to insert into the bucket. 169 func serializeResult(rp *paymentResult) ([]byte, []byte, error) { 170 // Write timestamps, success status, failure source index and route. 171 var b bytes.Buffer 172 173 var dbFailureSourceIdx int32 174 if rp.failureSourceIdx == nil { 175 dbFailureSourceIdx = unknownFailureSourceIdx 176 } else { 177 dbFailureSourceIdx = int32(*rp.failureSourceIdx) 178 } 179 180 err := channeldb.WriteElements( 181 &b, 182 uint64(rp.timeFwd.UnixNano()), 183 uint64(rp.timeReply.UnixNano()), 184 rp.success, dbFailureSourceIdx, 185 ) 186 if err != nil { 187 return nil, nil, err 188 } 189 190 if err := channeldb.SerializeRoute(&b, *rp.route); err != nil { 191 return nil, nil, err 192 } 193 194 // Write failure. If there is no failure message, write an empty 195 // byte slice. 196 var failureBytes bytes.Buffer 197 if rp.failure != nil { 198 err := lnwire.EncodeFailureMessage(&failureBytes, rp.failure, 0) 199 if err != nil { 200 return nil, nil, err 201 } 202 } 203 err = wire.WriteVarBytes(&b, 0, failureBytes.Bytes()) 204 if err != nil { 205 return nil, nil, err 206 } 207 208 // Compose key that identifies this result. 209 key := getResultKey(rp) 210 211 return key, b.Bytes(), nil 212 } 213 214 // deserializeResult deserializes a payment result. 215 func deserializeResult(k, v []byte) (*paymentResult, error) { 216 // Parse payment id. 217 result := paymentResult{ 218 id: byteOrder.Uint64(k[8:]), 219 } 220 221 r := bytes.NewReader(v) 222 223 // Read timestamps, success status and failure source index. 224 var ( 225 timeFwd, timeReply uint64 226 dbFailureSourceIdx int32 227 ) 228 229 err := channeldb.ReadElements( 230 r, &timeFwd, &timeReply, &result.success, &dbFailureSourceIdx, 231 ) 232 if err != nil { 233 return nil, err 234 } 235 236 // Convert time stamps to local time zone for consistent logging. 237 result.timeFwd = time.Unix(0, int64(timeFwd)).Local() 238 result.timeReply = time.Unix(0, int64(timeReply)).Local() 239 240 // Convert from unknown index magic number to nil value. 241 if dbFailureSourceIdx != unknownFailureSourceIdx { 242 failureSourceIdx := int(dbFailureSourceIdx) 243 result.failureSourceIdx = &failureSourceIdx 244 } 245 246 // Read route. 247 route, err := channeldb.DeserializeRoute(r) 248 if err != nil { 249 return nil, err 250 } 251 result.route = &route 252 253 // Read failure. 254 failureBytes, err := wire.ReadVarBytes( 255 r, 0, lnwire.FailureMessageLength, "failure", 256 ) 257 if err != nil { 258 return nil, err 259 } 260 if len(failureBytes) > 0 { 261 result.failure, err = lnwire.DecodeFailureMessage( 262 bytes.NewReader(failureBytes), 0, 263 ) 264 if err != nil { 265 return nil, err 266 } 267 } 268 269 return &result, nil 270 } 271 272 // AddResult adds a new result to the db. 273 func (b *missionControlStore) AddResult(rp *paymentResult) { 274 b.queueMx.Lock() 275 b.queue.PushBack(rp) 276 signalRun := b.queue.Len() == 1 277 b.queueMx.Unlock() 278 279 // Signal run() that the queue is non-empty. 280 if signalRun { 281 select { 282 case <-b.done: 283 case b.queueChan <- struct{}{}: 284 } 285 } 286 } 287 288 // stop stops the store ticker goroutine. 289 func (b *missionControlStore) stop() { 290 close(b.done) 291 b.wg.Wait() 292 } 293 294 // run runs the MC store ticker goroutine. 295 func (b *missionControlStore) run() { 296 b.wg.Add(1) 297 298 go func() { 299 // TimerChan is non-null when there are items to be stored. 300 var timerChan <-chan time.Time 301 defer b.wg.Done() 302 303 for { 304 select { 305 case <-b.queueChan: 306 timerChan = time.After(b.flushInterval) 307 308 case <-timerChan: 309 timerChan = nil 310 if err := b.storeResults(); err != nil { 311 log.Errorf("Failed to update mission "+ 312 "control store: %v", err) 313 } 314 315 case <-b.done: 316 return 317 } 318 } 319 }() 320 } 321 322 // storeResults stores all accumulated results. 323 func (b *missionControlStore) storeResults() error { 324 b.queueMx.Lock() 325 l := b.queue 326 327 // Only recreate queue if its not empty because if it is empty we will 328 // return early from the func without running any updates. 329 isEmpty := l.Len() == 0 330 if !isEmpty { 331 b.queue = list.New[*paymentResult]() 332 } 333 b.queueMx.Unlock() 334 335 // Return early when no new items will be updated. 336 if isEmpty { 337 return nil 338 } 339 340 // Create a deduped list of new entries. 341 newKeys := make(map[string]*paymentResult, l.Len()) 342 for e := l.Front(); e != nil; { 343 pr := e.Value 344 key := string(getResultKey(pr)) 345 if _, ok := b.keysMap[key]; ok { 346 e, _ = e.Next(), l.Remove(e) 347 continue 348 } 349 if _, ok := newKeys[key]; ok { 350 e, _ = e.Next(), l.Remove(e) 351 continue 352 } 353 newKeys[key] = pr 354 e = e.Next() 355 } 356 357 // Create a list of entries to delete. 358 toDelete := b.keys.Len() + len(newKeys) - b.maxRecords 359 var delKeys []string 360 if b.maxRecords > 0 && toDelete > 0 { 361 delKeys = make([]string, 0, toDelete) 362 363 // Delete as many as needed from old keys. 364 for e := b.keys.Front(); len(delKeys) < toDelete && e != nil; { 365 delKeys = append(delKeys, e.Value) 366 e = e.Next() 367 } 368 369 // If more deletions are needed, simply do not add from the 370 // list of new keys. 371 for e := l.Front(); len(delKeys) < toDelete && e != nil; { 372 toDelete-- 373 pr := e.Value 374 key := string(getResultKey(pr)) 375 delete(newKeys, key) 376 l.Remove(e) 377 e = l.Front() 378 } 379 } 380 381 var storeCount, pruneCount int 382 383 err := kvdb.Update(b.db, func(tx kvdb.RwTx) error { 384 bucket := tx.ReadWriteBucket(resultsKey) 385 386 for e := l.Front(); e != nil; e = e.Next() { 387 pr := e.Value 388 // Serialize result into key and value byte slices. 389 k, v, err := serializeResult(pr) 390 if err != nil { 391 return err 392 } 393 394 // Put into results bucket. 395 if err := bucket.Put(k, v); err != nil { 396 return err 397 } 398 399 storeCount++ 400 } 401 402 // Prune oldest entries. 403 for _, key := range delKeys { 404 if err := bucket.Delete([]byte(key)); err != nil { 405 return err 406 } 407 pruneCount++ 408 } 409 410 return nil 411 }, func() { 412 storeCount, pruneCount = 0, 0 413 }) 414 415 if err != nil { 416 return err 417 } 418 419 log.Debugf("Stored mission control results: %d added, %d deleted", 420 storeCount, pruneCount) 421 422 // DB Update was successful, update the in-memory cache. 423 for _, key := range delKeys { 424 delete(b.keysMap, key) 425 b.keys.Remove(b.keys.Front()) 426 } 427 for e := l.Front(); e != nil; e = e.Next() { 428 pr := e.Value 429 key := string(getResultKey(pr)) 430 b.keys.PushBack(key) 431 } 432 433 return nil 434 } 435 436 // getResultKey returns a byte slice representing a unique key for this payment 437 // result. 438 func getResultKey(rp *paymentResult) []byte { 439 var keyBytes [8 + 8 + 33]byte 440 441 // Identify records by a combination of time, payment id and sender pub 442 // key. This allows importing mission control data from an external 443 // source without key collisions and keeps the records sorted 444 // chronologically. 445 byteOrder.PutUint64(keyBytes[:], uint64(rp.timeReply.UnixNano())) 446 byteOrder.PutUint64(keyBytes[8:], rp.id) 447 copy(keyBytes[16:], rp.route.SourcePubKey[:]) 448 449 return keyBytes[:] 450 }