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  }