github.com/decred/dcrlnd@v0.7.6/channeldb/payments_inflight.go (about) 1 package channeldb 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/hex" 7 "errors" 8 "fmt" 9 10 "github.com/btcsuite/btcwallet/walletdb" 11 ) 12 13 var ( 14 // paymentsInflightIndexBucket is the name of the top-level bucket 15 // within the database that stores an index of payment hashes that are 16 // still inflight. 17 // 18 // This is used to speedup startup by having a faster way to determine 19 // payments that are still inflight. 20 paymentsInflightIndexBucket = []byte("payments-inflight-index-bucket") 21 22 // errPaymentsInflightIndexNotExists is used to signal that the 23 // inflight payments index is disabled. 24 errPaymentsInflightIndexNotExists = errors.New("payments inflight index does not exist") 25 ) 26 27 // paymentInflightIndexValueVersion is set as the value used in the inflight 28 // payments index. 29 const paymentInflightIndexValueVersion = 0 30 31 // createPaymentInflightIndexEntry marks a payment with the given sequence 32 // number as inflight. 33 func createPaymentInflightIndexEntry(tx walletdb.ReadWriteTx, seqNum []byte) error { 34 indexBucket := tx.ReadWriteBucket(paymentsInflightIndexBucket) 35 36 // If the bucket doesn't exist, indexing of inflight payments is disabled. 37 if indexBucket == nil { 38 return nil 39 } 40 41 v := []byte{paymentInflightIndexValueVersion} 42 err := indexBucket.Put(seqNum, v) 43 if err != nil { 44 return fmt.Errorf("unable to mark inflight: %v", err) 45 } 46 return err 47 } 48 49 // deletePaymentInflightIndexEntry marks a payment with the given sequence 50 // number as not inflight. 51 func deletePaymentInflightIndexEntry(tx walletdb.ReadWriteTx, seqNum []byte) error { 52 indexBucket := tx.ReadWriteBucket(paymentsInflightIndexBucket) 53 54 // If the bucket doesn't exist, the entry can't be removed. 55 if indexBucket == nil { 56 return nil 57 } 58 59 return indexBucket.Delete(seqNum) 60 } 61 62 // updatePaymentInflightIndexEntry updates the entry of the given payment 63 // in the inflight payments index based on the payment's status. 64 func updatePaymentInflightIndexEntry(tx walletdb.ReadWriteTx, payment *MPPayment) error { 65 var seqBytes [8]byte 66 binary.BigEndian.PutUint64(seqBytes[:], payment.SequenceNum) 67 68 if payment.Status == StatusInFlight { 69 return createPaymentInflightIndexEntry(tx, seqBytes[:]) 70 } 71 72 return deletePaymentInflightIndexEntry(tx, seqBytes[:]) 73 } 74 75 // recreatePaymentsInflightIndex recreates the index of inflight payments in 76 // the DB. 77 func recreatePaymentsInflightIndex(tx walletdb.ReadWriteTx) error { 78 indexBucket := tx.ReadWriteBucket(paymentsInflightIndexBucket) 79 if indexBucket == nil { 80 var err error 81 _, err = tx.CreateTopLevelBucket(paymentsInflightIndexBucket) 82 if err != nil { 83 return err 84 } 85 } 86 87 payments := tx.ReadBucket(paymentsRootBucket) 88 if payments == nil { 89 return nil 90 } 91 92 var nbPayments, nbInflight uint64 93 err := payments.ForEach(func(k, _ []byte) error { 94 bucket := payments.NestedReadBucket(k) 95 if bucket == nil { 96 return fmt.Errorf("non bucket element") 97 } 98 99 p, err := fetchPayment(bucket) 100 if err != nil { 101 return err 102 } 103 104 nbPayments += 1 105 if p.Status == StatusInFlight { 106 nbInflight += 1 107 } 108 109 return updatePaymentInflightIndexEntry(tx, p) 110 }) 111 if err != nil { 112 return err 113 } 114 115 log.Infof("Recreated in-flight payments index: %d payments, %d in-flight", 116 nbPayments, nbInflight) 117 118 return nil 119 } 120 121 // fetchInflightPaymentsByIndex fetches the list of inflight payments using the 122 // inflight payments index. If the index is disabled, errPaymentsInflightIndexNotExists 123 // is returned. 124 func fetchInflightPaymentsByIndex(tx walletdb.ReadTx) ([]*MPPayment, error) { 125 indexBucket := tx.ReadBucket(paymentsInflightIndexBucket) 126 127 // If the bucket doesn't exist, notify caller. 128 if indexBucket == nil { 129 return nil, errPaymentsInflightIndexNotExists 130 } 131 132 payments := tx.ReadBucket(paymentsRootBucket) 133 if payments == nil { 134 return nil, fmt.Errorf("payments root bucket does not exist") 135 } 136 137 seqIndexes := tx.ReadBucket(paymentsIndexBucket) 138 if seqIndexes == nil { 139 return nil, fmt.Errorf("index bucket does not exist") 140 } 141 142 var inFlights []*MPPayment 143 err := indexBucket.ForEach(func(k, v []byte) error { 144 hash := seqIndexes.Get(k) 145 if hash == nil { 146 return fmt.Errorf("seqnum %s does not exist in index db", 147 hex.EncodeToString(k)) 148 } 149 r := bytes.NewReader(hash) 150 paymentHash, err := deserializePaymentIndex(r) 151 if err != nil { 152 return err 153 } 154 155 bucket := payments.NestedReadBucket(paymentHash[:]) 156 p, err := fetchPayment(bucket) 157 if err != nil { 158 return err 159 } 160 161 // Double check our assumption that this payment is in fact 162 // in-flight. If it is not, we warn about an inconsistency 163 // but otherwise don't completely fail to load the inflight 164 // payments to avoid breaking the ability to open the app. 165 if p.Status != StatusInFlight { 166 log.Warnf("Broken assumption: Payment hash %s (seqnum %d) "+ 167 "in inflight payments index is not actually "+ 168 "inflight (status %s - %d)", 169 paymentHash, p.SequenceNum, p.Status, p.Status) 170 return nil 171 } 172 173 inFlights = append(inFlights, p) 174 return nil 175 }) 176 return inFlights, err 177 }