github.com/decred/dcrlnd@v0.7.6/channeldb/reports.go (about) 1 package channeldb 2 3 import ( 4 "bytes" 5 "errors" 6 "io" 7 8 "github.com/decred/dcrd/chaincfg/chainhash" 9 "github.com/decred/dcrd/dcrutil/v4" 10 "github.com/decred/dcrd/wire" 11 "github.com/decred/dcrlnd/kvdb" 12 "github.com/decred/dcrlnd/tlv" 13 ) 14 15 var ( 16 // closeSummaryBucket is a top level bucket which holds additional 17 // information about channel closes. It nests channels by chainhash 18 // and channel point. 19 // [closeSummaryBucket] 20 // [chainHashBucket] 21 // [channelBucket] 22 // [resolversBucket] 23 closeSummaryBucket = []byte("close-summaries") 24 25 // resolversBucket holds the outcome of a channel's resolvers. It is 26 // nested under a channel and chainhash bucket in the close summaries 27 // bucket. 28 resolversBucket = []byte("resolvers-bucket") 29 ) 30 31 var ( 32 // ErrNoChainHashBucket is returned when we have not created a bucket 33 // for the current chain hash. 34 ErrNoChainHashBucket = errors.New("no chain hash bucket") 35 36 // ErrNoChannelSummaries is returned when a channel is not found in the 37 // chain hash bucket. 38 ErrNoChannelSummaries = errors.New("channel bucket not found") 39 40 amountType tlv.Type = 1 41 resolverType tlv.Type = 2 42 outcomeType tlv.Type = 3 43 spendTxIDType tlv.Type = 4 44 ) 45 46 // ResolverType indicates the type of resolver that was resolved on chain. 47 type ResolverType uint8 48 49 const ( 50 // ResolverTypeAnchor represents a resolver for an anchor output. 51 ResolverTypeAnchor ResolverType = 0 52 53 // ResolverTypeIncomingHtlc represents resolution of an incoming htlc. 54 ResolverTypeIncomingHtlc ResolverType = 1 55 56 // ResolverTypeOutgoingHtlc represents resolution of an outgoing htlc. 57 ResolverTypeOutgoingHtlc ResolverType = 2 58 59 // ResolverTypeCommit represents resolution of our time locked commit 60 // when we force close. 61 ResolverTypeCommit ResolverType = 3 62 ) 63 64 // ResolverOutcome indicates the outcome for the resolver that that the contract 65 // court reached. This state is not necessarily final, since htlcs on our own 66 // commitment are resolved across two resolvers. 67 type ResolverOutcome uint8 68 69 const ( 70 // ResolverOutcomeClaimed indicates that funds were claimed on chain. 71 ResolverOutcomeClaimed ResolverOutcome = 0 72 73 // ResolverOutcomeUnclaimed indicates that we did not claim our funds on 74 // chain. This may be the case for anchors that we did not sweep, or 75 // outputs that were not economical to sweep. 76 ResolverOutcomeUnclaimed ResolverOutcome = 1 77 78 // ResolverOutcomeAbandoned indicates that we did not attempt to claim 79 // an output on chain. This is the case for htlcs that we could not 80 // decode to claim, or invoice which we fail when an attempt is made 81 // to settle them on chain. 82 ResolverOutcomeAbandoned ResolverOutcome = 2 83 84 // ResolverOutcomeTimeout indicates that a contract was timed out on 85 // chain. 86 ResolverOutcomeTimeout ResolverOutcome = 3 87 88 // ResolverOutcomeFirstStage indicates that a htlc had to be claimed 89 // over two stages, with this outcome representing the confirmation 90 // of our success/timeout tx. 91 ResolverOutcomeFirstStage ResolverOutcome = 4 92 ) 93 94 // ResolverReport provides an account of the outcome of a resolver. This differs 95 // from a ContractReport because it does not necessarily fully resolve the 96 // contract; each step of two stage htlc resolution is included. 97 type ResolverReport struct { 98 // OutPoint is the on chain outpoint that was spent as a result of this 99 // resolution. When an output is directly resolved (eg, commitment 100 // sweeps and single stage htlcs on the remote party's output) this 101 // is an output on the commitment tx that was broadcast. When we resolve 102 // across two stages (eg, htlcs on our own force close commit), the 103 // first stage outpoint is the output on our commitment and the second 104 // stage output is the spend from our htlc success/timeout tx. 105 OutPoint wire.OutPoint 106 107 // Amount is the value of the output referenced above. 108 Amount dcrutil.Amount 109 110 // ResolverType indicates the type of resolution that occurred. 111 ResolverType 112 113 // ResolverOutcome indicates the outcome of the resolver. 114 ResolverOutcome 115 116 // SpendTxID is the transaction ID of the spending transaction that 117 // claimed the outpoint. This may be a sweep transaction, or a first 118 // stage success/timeout transaction. 119 SpendTxID *chainhash.Hash 120 } 121 122 // PutResolverReport creates and commits a transaction that is used to write a 123 // resolver report to disk. 124 func (d *DB) PutResolverReport(tx kvdb.RwTx, chainHash chainhash.Hash, 125 channelOutpoint *wire.OutPoint, report *ResolverReport) error { 126 127 putReportFunc := func(tx kvdb.RwTx) error { 128 return putReport(tx, chainHash, channelOutpoint, report) 129 } 130 131 // If the transaction is nil, we'll create a new one. 132 if tx == nil { 133 return kvdb.Update(d, putReportFunc, func() {}) 134 } 135 136 // Otherwise, we can write the report to disk using the existing 137 // transaction. 138 return putReportFunc(tx) 139 } 140 141 // putReport puts a report in the bucket provided, with its outpoint as its key. 142 func putReport(tx kvdb.RwTx, chainHash chainhash.Hash, 143 channelOutpoint *wire.OutPoint, report *ResolverReport) error { 144 145 channelBucket, err := fetchReportWriteBucket( 146 tx, chainHash, channelOutpoint, 147 ) 148 if err != nil { 149 return err 150 } 151 152 // If the resolvers bucket does not exist yet, create it. 153 resolvers, err := channelBucket.CreateBucketIfNotExists( 154 resolversBucket, 155 ) 156 if err != nil { 157 return err 158 } 159 160 var valueBuf bytes.Buffer 161 if err := serializeReport(&valueBuf, report); err != nil { 162 return err 163 } 164 165 // Finally write our outpoint to be used as the key for this record. 166 var keyBuf bytes.Buffer 167 if err := writeOutpoint(&keyBuf, &report.OutPoint); err != nil { 168 return err 169 } 170 171 return resolvers.Put(keyBuf.Bytes(), valueBuf.Bytes()) 172 } 173 174 // serializeReport serialized a report using a TLV stream to allow for optional 175 // fields. 176 func serializeReport(w io.Writer, report *ResolverReport) error { 177 amt := uint64(report.Amount) 178 resolver := uint8(report.ResolverType) 179 outcome := uint8(report.ResolverOutcome) 180 181 // Create a set of TLV records for the values we know to be present. 182 records := []tlv.Record{ 183 tlv.MakePrimitiveRecord(amountType, &amt), 184 tlv.MakePrimitiveRecord(resolverType, &resolver), 185 tlv.MakePrimitiveRecord(outcomeType, &outcome), 186 } 187 188 // If our spend txid is non-nil, we add a tlv entry for it. 189 if report.SpendTxID != nil { 190 var spendBuf bytes.Buffer 191 err := WriteElement(&spendBuf, *report.SpendTxID) 192 if err != nil { 193 return err 194 } 195 spendBytes := spendBuf.Bytes() 196 197 records = append(records, tlv.MakePrimitiveRecord( 198 spendTxIDType, &spendBytes, 199 )) 200 } 201 202 // Create our stream and encode it. 203 tlvStream, err := tlv.NewStream(records...) 204 if err != nil { 205 return err 206 } 207 208 return tlvStream.Encode(w) 209 } 210 211 // FetchChannelReports fetches the set of reports for a channel. 212 func (d *DB) FetchChannelReports(chainHash chainhash.Hash, 213 outPoint *wire.OutPoint) ([]*ResolverReport, error) { 214 215 var reports []*ResolverReport 216 217 if err := kvdb.View(d.Backend, func(tx kvdb.RTx) error { 218 chanBucket, err := fetchReportReadBucket( 219 tx, chainHash, outPoint, 220 ) 221 if err != nil { 222 return err 223 } 224 225 // If there are no resolvers for this channel, we simply 226 // return nil, because nothing has been persisted yet. 227 resolvers := chanBucket.NestedReadBucket(resolversBucket) 228 if resolvers == nil { 229 return nil 230 } 231 232 // Run through each resolution and add it to our set of 233 // resolutions. 234 return resolvers.ForEach(func(k, v []byte) error { 235 // Deserialize the contents of our field. 236 r := bytes.NewReader(v) 237 report, err := deserializeReport(r) 238 if err != nil { 239 return err 240 } 241 242 // Once we have read our values out, set the outpoint 243 // on the report using the key. 244 r = bytes.NewReader(k) 245 if err := ReadElement(r, &report.OutPoint); err != nil { 246 return err 247 } 248 249 reports = append(reports, report) 250 251 return nil 252 }) 253 }, func() { 254 reports = nil 255 }); err != nil { 256 return nil, err 257 } 258 259 return reports, nil 260 } 261 262 // deserializeReport gets a resolver report from a tlv stream. The outpoint on 263 // the resolver will not be set because we key reports by their outpoint, and 264 // this function reads only the values saved in the stream. 265 func deserializeReport(r io.Reader) (*ResolverReport, error) { 266 var ( 267 resolver, outcome uint8 268 amt uint64 269 spentTx []byte 270 ) 271 272 tlvStream, err := tlv.NewStream( 273 tlv.MakePrimitiveRecord(amountType, &amt), 274 tlv.MakePrimitiveRecord(resolverType, &resolver), 275 tlv.MakePrimitiveRecord(outcomeType, &outcome), 276 tlv.MakePrimitiveRecord(spendTxIDType, &spentTx), 277 ) 278 if err != nil { 279 return nil, err 280 } 281 282 if err := tlvStream.Decode(r); err != nil { 283 return nil, err 284 } 285 286 report := &ResolverReport{ 287 Amount: dcrutil.Amount(amt), 288 ResolverOutcome: ResolverOutcome(outcome), 289 ResolverType: ResolverType(resolver), 290 } 291 292 // If our spend tx is set, we set it on our report. 293 if len(spentTx) != 0 { 294 spendTx, err := chainhash.NewHash(spentTx) 295 if err != nil { 296 return nil, err 297 } 298 report.SpendTxID = spendTx 299 } 300 301 return report, nil 302 } 303 304 // fetchReportWriteBucket returns a write channel bucket within the reports 305 // top level bucket. If the channel's bucket does not yet exist, it will be 306 // created. 307 func fetchReportWriteBucket(tx kvdb.RwTx, chainHash chainhash.Hash, 308 outPoint *wire.OutPoint) (kvdb.RwBucket, error) { 309 310 // Get the channel close summary bucket. 311 closedBucket := tx.ReadWriteBucket(closeSummaryBucket) 312 313 // Create the chain hash bucket if it does not exist. 314 chainHashBkt, err := closedBucket.CreateBucketIfNotExists(chainHash[:]) 315 if err != nil { 316 return nil, err 317 } 318 319 var chanPointBuf bytes.Buffer 320 if err := writeOutpoint(&chanPointBuf, outPoint); err != nil { 321 return nil, err 322 } 323 324 return chainHashBkt.CreateBucketIfNotExists(chanPointBuf.Bytes()) 325 } 326 327 // fetchReportReadBucket returns a read channel bucket within the reports 328 // top level bucket. If any bucket along the way does not exist, it will error. 329 func fetchReportReadBucket(tx kvdb.RTx, chainHash chainhash.Hash, 330 outPoint *wire.OutPoint) (kvdb.RBucket, error) { 331 332 // First fetch the top level channel close summary bucket. 333 closeBucket := tx.ReadBucket(closeSummaryBucket) 334 335 // Next we get the chain hash bucket for our current chain. 336 chainHashBucket := closeBucket.NestedReadBucket(chainHash[:]) 337 if chainHashBucket == nil { 338 return nil, ErrNoChainHashBucket 339 } 340 341 // With the bucket for the node and chain fetched, we can now go down 342 // another level, for the channel itself. 343 var chanPointBuf bytes.Buffer 344 if err := writeOutpoint(&chanPointBuf, outPoint); err != nil { 345 return nil, err 346 } 347 348 chanBucket := chainHashBucket.NestedReadBucket(chanPointBuf.Bytes()) 349 if chanBucket == nil { 350 return nil, ErrNoChannelSummaries 351 } 352 353 return chanBucket, nil 354 }