Skip to content

Commit

Permalink
Merge 690eaf5 into c1ef5bb
Browse files Browse the repository at this point in the history
  • Loading branch information
carlaKC committed Jun 26, 2020
2 parents c1ef5bb + 690eaf5 commit aa19c62
Show file tree
Hide file tree
Showing 25 changed files with 3,086 additions and 918 deletions.
7 changes: 7 additions & 0 deletions channeldb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ var (
number: 16,
migration: migration16.MigrateSequenceIndex,
},
{
// Create a top level bucket which will store extra
// information about channel closes.
number: 17,
migration: mig.CreateTLB(closeSummaryBucket),
},
}

// Big endian is the preferred byte order, due to cursor scans over
Expand Down Expand Up @@ -277,6 +283,7 @@ var topLevelBuckets = [][]byte{
edgeIndexBucket,
graphMetaBucket,
metaBucket,
closeSummaryBucket,
}

// Wipe completely deletes all saved state within all used buckets within the
Expand Down
353 changes: 353 additions & 0 deletions channeldb/reports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,353 @@
package channeldb

import (
"bytes"
"errors"

"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb/kvdb"
)

var (
// closeSummaryBucket is a top level bucket which holds additional
// information about channel closes. It nests channels by chainhash
// and channel point.
// [closeSummaryBucket]
// [chainHashBucket]
// [channelBucket]
// [resolversBucket]
closeSummaryBucket = []byte("close-summaries")

// resolversBucket holds the outcome of a channel's resolvers. It is
// nested under a channel and chainhash bucket in the close summaries
// bucket.
resolversBucket = []byte("resolvers-bucket")
)

var (
// ErrNoCloseSummaryBucket is returned if the report bucket has not been
// created yet.
ErrNoCloseSummaryBucket = errors.New("no channel close summary bucket " +
"present")

// ErrNoChainHashBucket is returned when we have not created a bucket
// for the current chain hash.
ErrNoChainHashBucket = errors.New("no chain hash bucket")

// ErrNoChannelSummaries is returned when a channel is not found in the
// chain hash bucket.
ErrNoChannelSummaries = errors.New("channel bucket not found")
)

// ResolverType indicates the type of resolver that was resolved on chain.
type ResolverType uint8

const (
// ResolverTypeUnknown is a placeholder for unknown resolver types.
ResolverTypeUnknown ResolverType = iota

// ResolverTypeAnchor represents a resolver for an anchor output.
ResolverTypeAnchor

// ResolverTypeIncomingHtlc represents resolution of an incoming htlc.
ResolverTypeIncomingHtlc

// ResolverTypeOutgoingHtlc represents resolution of an outgoing htlc.
ResolverTypeOutgoingHtlc

// ResolverTypeCommit represents resolution of our time locked commit
// when we force close.
ResolverTypeCommit
)

// ResolverOutcome indicates the outcome for the resolver that that the contract
// court reached. This state is not necessarily final, since htlcs on our own
// commitment are resolved across two resolvers.
type ResolverOutcome uint8

const (
// ResolverOutcomeUnknown is a placeholder for unknown resolver
// outcomes.
ResolverOutcomeUnknown ResolverOutcome = iota

// ResolverOutcomeClaimed indicates that funds were claimed on chain.
ResolverOutcomeClaimed

// ResolverOutcomeUnclaimed indicates that we did not claim our funds on
// chain. This may be the case for anchors that we did not sweep, or
// outputs that were not economical to sweep.
ResolverOutcomeUnclaimed

// ResolverOutcomeLost indicates that an output that we could have
// claimed, for example and anchor where anybody can spend, was claimed
// by another party.
ResolverOutcomeLost

// ResolverOutcomeFirstStage indicates that we broadcast the first stage
// of a two stage htlc claim.
ResolverOutcomeFirstStage

// ResolverOutcomeInvalid indicates that we deemed contract invalid
// and did not try to resolve it further. This is the case for htlcs
// that we cannot decode.
ResolverOutcomeInvalid

// ResolverOutcomeTimeout indicates that we timed out a contract on
// chain.
ResolverOutcomeTimeout

// ResolverOutcomeFailed indicates that we failed an attempt to settle
// a contract on chain.
ResolverOutcomeFailed
)

// ResolverReport provides an account of the outcome of a resolver. This differs
// from a ContractReport because it does not necessarily fully resolve the
// contract; each step of two stage htlc resolution is included.
type ResolverReport struct {
// OutPoint is the on chain outpoint that was spent as a result of this
// resolution. When an output is directly resolved (eg, commitment
// sweeps and single stage htlcs on the remote party's output) this
// is an output on the commitment tx that was broadcast. When we resolve
// across two stages (eg, htlcs on our own force close commit), the
// first stage output is the output on our commitment and the second
// stage output is the spend from our htlc success/timeout tx.
OutPoint wire.OutPoint

// Amount is the value of the output referenced above.
Amount btcutil.Amount

// ResolverType indicates the type of resolution that occurred.
ResolverType

// ResolverOutcome indicates the outcome of the resolver.
ResolverOutcome

// SpendTxID is the transaction ID of the spending transaction that
// claimed the outpoint. This may be a sweep transaction, or a first
// stage success/timeout transaction.
SpendTxID *chainhash.Hash
}

// PutResolverReport creates and commits a transaction that is used to write a
// resolver report to disk.
func (d *DB) PutResolverReport(tx kvdb.RwTx, chainHash chainhash.Hash,
channelOutpoint *wire.OutPoint, report *ResolverReport) error {

putReportFunc := func(tx kvdb.RwTx) error {
return putReport(tx, chainHash, channelOutpoint, report)
}

// If the transaction is nil, we'll create a new one.
if tx == nil {
return kvdb.Update(d, putReportFunc)
}

// Otherwise, we can write the report to disk using the existing
// transaction.
return putReportFunc(tx)
}

// putReport puts a report in the bucket provided, with its outpoint as its key.
func putReport(tx kvdb.RwTx, chainHash chainhash.Hash,
channelOutpoint *wire.OutPoint, report *ResolverReport) error {

channelBucket, err := fetchReportWriteBucket(
tx, chainHash, channelOutpoint,
)
if err != nil {
return err
}

// If the resolvers bucket does not exist yet, create it.
resolvers, err := channelBucket.CreateBucketIfNotExists(
resolversBucket,
)
if err != nil {
return err
}

var outPointBuf bytes.Buffer
if err := writeOutpoint(&outPointBuf, &report.OutPoint); err != nil {
return err
}

// Check whether we have a spend txid present so that we can write a
// boolean to disk signalling whether to expect a spend txid or not.
var hasSpendTxID bool
if report.SpendTxID != nil {
hasSpendTxID = true
}

var resolverBuf bytes.Buffer

if err := WriteElements(
&resolverBuf, uint8(report.ResolverType),
uint8(report.ResolverOutcome), report.Amount,
hasSpendTxID,
); err != nil {
return err
}

// If there is a spend txid present, we also write it to our buffer.
if hasSpendTxID {
err = WriteElement(&resolverBuf, *report.SpendTxID)
if err != nil {
return err
}
}

// We do not yet have fees available for these resolutions, so we write
// false to disk to indicate that this record does not have fee
// information. This allows us to start writing fees without needing to
// do an on the fly migration.
// TODO(carla): add fees to resolver report and write to disk.
if err = WriteElement(&resolverBuf, false); err != nil {
return err
}

return resolvers.Put(outPointBuf.Bytes(), resolverBuf.Bytes())
}

// FetchChannelReports fetches the set of reports for a channel.
func (d DB) FetchChannelReports(chainHash chainhash.Hash,
outPoint *wire.OutPoint) ([]*ResolverReport, error) {

var reports []*ResolverReport

if err := kvdb.View(d, func(tx kvdb.RTx) error {
chanBucket, err := fetchReportReadBucket(
tx, chainHash, outPoint,
)
if err != nil {
return err
}

// If there are no resolvers for this channel, we simply
// return nil, because nothing has been persisted yet.
resolvers := chanBucket.NestedReadBucket(resolversBucket)
if resolvers == nil {
return nil
}

// Run through each resolution and add it to our set of
// resolutions.
return resolvers.ForEach(func(k, v []byte) error {
var report ResolverReport

// Read our resolver key into the Report's outpoint
// from the entry's key.
r := bytes.NewReader(k)
err := ReadElement(r, &report.OutPoint)
if err != nil {
return err
}

// Next, we read our amount, outcome enum and whether
// we have a spend tx from disk.
r = bytes.NewReader(v)

var (
hasSpendTxID bool
resolverType, outcome uint8
)
if err := ReadElements(
r, &resolverType, &outcome, &report.Amount,
&hasSpendTxID,
); err != nil {
return err
}

report.ResolverType = ResolverType(resolverType)
report.ResolverOutcome = ResolverOutcome(outcome)

// If the report has a spend transaction present, read
// it.
if hasSpendTxID {
var spendTxID chainhash.Hash
err = ReadElement(r, &spendTxID)
if err != nil {
return err
}
report.SpendTxID = &spendTxID
}

// Finally, we read out a boolean that indicates
// whether we have our fee recorded on disk. Fees are
// not currently written to disk, but we read this
// value out for completeness.
var haveFee bool
if err = ReadElement(r, &haveFee); err != nil {
return err
}

reports = append(reports, &report)

return nil
})
}); err != nil {
return nil, err
}

return reports, nil
}

// fetchReportWriteBucket returns a write channel bucket within the reports
// top level bucket. If the channel's bucket does not yet exist, it will be
// created.
func fetchReportWriteBucket(tx kvdb.RwTx, chainHash chainhash.Hash,
outPoint *wire.OutPoint) (kvdb.RwBucket, error) {

// Get the channel close summary bucket.
closedBucket := tx.ReadWriteBucket(closeSummaryBucket)
if closedBucket == nil {
return nil, ErrNoCloseSummaryBucket
}

// Create the chain hash bucket if it does not exist.
chainHashBkt, err := closedBucket.CreateBucketIfNotExists(chainHash[:])
if err != nil {
return nil, err
}

var chanPointBuf bytes.Buffer
if err := writeOutpoint(&chanPointBuf, outPoint); err != nil {
return nil, err
}

return chainHashBkt.CreateBucketIfNotExists(chanPointBuf.Bytes())
}

// fetchReportReadBucket returns a read channel bucket within the reports
// top level bucket. If any bucket along the way does not exist, it will error.
func fetchReportReadBucket(tx kvdb.RTx, chainHash chainhash.Hash,
outPoint *wire.OutPoint) (kvdb.RBucket, error) {

// First fetch the top level channel close summary bucket.
closeBucket := tx.ReadBucket(closeSummaryBucket)
if closeBucket == nil {
return nil, ErrNoCloseSummaryBucket
}

// Next we get the chain hash bucket for our current chain.
chainHashBucket := closeBucket.NestedReadBucket(chainHash[:])
if chainHashBucket == nil {
return nil, ErrNoChainHashBucket
}

// With the bucket for the node and chain fetched, we can now go down
// another level, for the channel itself.
var chanPointBuf bytes.Buffer
if err := writeOutpoint(&chanPointBuf, outPoint); err != nil {
return nil, err
}

chanBucket := chainHashBucket.NestedReadBucket(chanPointBuf.Bytes())
if chanBucket == nil {
return nil, ErrNoChannelSummaries
}

return chanBucket, nil
}

0 comments on commit aa19c62

Please sign in to comment.