package channeldb import ( "bytes" "encoding/binary" "errors" "fmt" "io" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" ) // ErrCorruptedFwdPkg signals that the on-disk structure of the forwarding // package has potentially been mangled. var ErrCorruptedFwdPkg = errors.New("fwding package db has been corrupted") // FwdState is an enum used to describe the lifecycle of a FwdPkg. type FwdState byte const ( // FwdStateLockedIn is the starting state for all forwarding packages. // Packages in this state have not yet committed to the exact set of // Adds to forward to the switch. FwdStateLockedIn FwdState = iota // FwdStateProcessed marks the state in which all Adds have been // locally processed and the forwarding decision to the switch has been // persisted. FwdStateProcessed // FwdStateCompleted signals that all Adds have been acked, and that all // settles and fails have been delivered to their sources. Packages in // this state can be removed permanently. FwdStateCompleted ) var ( // fwdPackagesKey is the root-level bucket that all forwarding packages // are written. This bucket is further subdivided based on the short // channel ID of each channel. fwdPackagesKey = []byte("fwd-packages") // addBucketKey is the bucket to which all Add log updates are written. addBucketKey = []byte("add-updates") // failSettleBucketKey is the bucket to which all Settle/Fail log // updates are written. failSettleBucketKey = []byte("fail-settle-updates") // fwdFilterKey is a key used to write the set of Adds that passed // validation and are to be forwarded to the switch. // NOTE: The presence of this key within a forwarding package indicates // that the package has reached FwdStateProcessed. fwdFilterKey = []byte("fwd-filter-key") // ackFilterKey is a key used to access the PkgFilter indicating which // Adds have received a Settle/Fail. This response may come from a // number of sources, including: exitHop settle/fails, switch failures, // chain arbiter interjections, as well as settle/fails from the // next hop in the route. ackFilterKey = []byte("ack-filter-key") // settleFailFilterKey is a key used to access the PkgFilter indicating // which Settles/Fails in have been received and processed by the link // that originally received the Add. settleFailFilterKey = []byte("settle-fail-filter-key") ) // PkgFilter is used to compactly represent a particular subset of the Adds in a // forwarding package. Each filter is represented as a simple, statically-sized // bitvector, where the elements are intended to be the indices of the Adds as // they are written in the FwdPkg. type PkgFilter struct { count uint16 filter []byte } // NewPkgFilter initializes an empty PkgFilter supporting `count` elements. func NewPkgFilter(count uint16) *PkgFilter { // We add 7 to ensure that the integer division yields properly rounded // values. filterLen := (count + 7) / 8 return &PkgFilter{ count: count, filter: make([]byte, filterLen), } } // Count returns the number of elements represented by this PkgFilter. func (f *PkgFilter) Count() uint16 { return f.count } // Set marks the `i`-th element as included by this filter. // NOTE: It is assumed that i is always less than count. func (f *PkgFilter) Set(i uint16) { byt := i / 8 bit := i % 8 // Set the i-th bit in the filter. // TODO(conner): ignore if > count to prevent panic? f.filter[byt] |= byte(1 << (7 - bit)) } // Contains queries the filter for membership of index `i`. // NOTE: It is assumed that i is always less than count. func (f *PkgFilter) Contains(i uint16) bool { byt := i / 8 bit := i % 8 // Read the i-th bit in the filter. // TODO(conner): ignore if > count to prevent panic? return f.filter[byt]&(1<<(7-bit)) != 0 } // Equal checks two PkgFilters for equality. func (f *PkgFilter) Equal(f2 *PkgFilter) bool { if f == f2 { return true } if f.count != f2.count { return false } return bytes.Equal(f.filter, f2.filter) } // IsFull returns true if every element in the filter has been Set, and false // otherwise. func (f *PkgFilter) IsFull() bool { // Batch validate bytes that are fully used. for i := uint16(0); i < f.count/8; i++ { if f.filter[i] != 0xFF { return false } } // If the count is not a multiple of 8, check that the filter contains // all remaining bits. rem := f.count % 8 for idx := f.count - rem; idx < f.count; idx++ { if !f.Contains(idx) { return false } } return true } // Size returns number of bytes produced when the PkgFilter is serialized. func (f *PkgFilter) Size() uint16 { // 2 bytes for uint16 `count`, then round up number of bytes required to // represent `count` bits. return 2 + (f.count+7)/8 } // Encode writes the filter to the provided io.Writer. func (f *PkgFilter) Encode(w io.Writer) error { if err := binary.Write(w, binary.BigEndian, f.count); err != nil { return err } _, err := w.Write(f.filter) return err } // Decode reads the filter from the provided io.Reader. func (f *PkgFilter) Decode(r io.Reader) error { if err := binary.Read(r, binary.BigEndian, &f.count); err != nil { return err } f.filter = make([]byte, f.Size()-2) _, err := io.ReadFull(r, f.filter) return err } // FwdPkg records all adds, settles, and fails that were locked in as a result // of the remote peer sending us a revocation. Each package is identified by // the short chanid and remote commitment height corresponding to the revocation // that locked in the HTLCs. For everything except a locally initiated payment, // settles and fails in a forwarding package must have a corresponding Add in // another package, and can be removed individually once the source link has // received the fail/settle. // // Adds cannot be removed, as we need to present the same batch of Adds to // properly handle replay protection. Instead, we use a PkgFilter to mark that // we have finished processing a particular Add. A FwdPkg should only be deleted // after the AckFilter is full and all settles and fails have been persistently // removed. type FwdPkg struct { // Source identifies the channel that wrote this forwarding package. Source lnwire.ShortChannelID // Height is the height of the remote commitment chain that locked in // this forwarding package. Height uint64 // State signals the persistent condition of the package and directs how // to reprocess the package in the event of failures. State FwdState // Adds contains all add messages which need to be processed and // forwarded to the switch. Adds does not change over the life of a // forwarding package. Adds []LogUpdate // FwdFilter is a filter containing the indices of all Adds that were // forwarded to the switch. FwdFilter *PkgFilter // AckFilter is a filter containing the indices of all Adds for which // the source has received a settle or fail and is reflected in the next // commitment txn. A package should not be removed until IsFull() // returns true. AckFilter *PkgFilter // SettleFails contains all settle and fail messages that should be // forwarded to the switch. SettleFails []LogUpdate // SettleFailFilter is a filter containing the indices of all Settle or // Fails originating in this package that have been received and locked // into the incoming link's commitment state. SettleFailFilter *PkgFilter } // NewFwdPkg initializes a new forwarding package in FwdStateLockedIn. This // should be used to create a package at the time we receive a revocation. func NewFwdPkg(source lnwire.ShortChannelID, height uint64, addUpdates, settleFailUpdates []LogUpdate) *FwdPkg { nAddUpdates := uint16(len(addUpdates)) nSettleFailUpdates := uint16(len(settleFailUpdates)) return &FwdPkg{ Source: source, Height: height, State: FwdStateLockedIn, Adds: addUpdates, FwdFilter: NewPkgFilter(nAddUpdates), AckFilter: NewPkgFilter(nAddUpdates), SettleFails: settleFailUpdates, SettleFailFilter: NewPkgFilter(nSettleFailUpdates), } } // ID returns an unique identifier for this package, used to ensure that sphinx // replay processing of this batch is idempotent. func (f *FwdPkg) ID() []byte { var id = make([]byte, 16) byteOrder.PutUint64(id[:8], f.Source.ToUint64()) byteOrder.PutUint64(id[8:], f.Height) return id } // String returns a human-readable description of the forwarding package. func (f *FwdPkg) String() string { return fmt.Sprintf("%T(src=%v, height=%v, nadds=%v, nfailsettles=%v)", f, f.Source, f.Height, len(f.Adds), len(f.SettleFails)) } // AddRef is used to identify a particular Add in a FwdPkg. The short channel ID // is assumed to be that of the packager. type AddRef struct { // Height is the remote commitment height that locked in the Add. Height uint64 // Index is the index of the Add within the fwd pkg's Adds. // // NOTE: This index is static over the lifetime of a forwarding package. Index uint16 } // Encode serializes the AddRef to the given io.Writer. func (a *AddRef) Encode(w io.Writer) error { if err := binary.Write(w, binary.BigEndian, a.Height); err != nil { return err } return binary.Write(w, binary.BigEndian, a.Index) } // Decode deserializes the AddRef from the given io.Reader. func (a *AddRef) Decode(r io.Reader) error { if err := binary.Read(r, binary.BigEndian, &a.Height); err != nil { return err } return binary.Read(r, binary.BigEndian, &a.Index) } // SettleFailRef is used to locate a Settle/Fail in another channel's FwdPkg. A // channel does not remove its own Settle/Fail htlcs, so the source is provided // to locate a db bucket belonging to another channel. type SettleFailRef struct { // Source identifies the outgoing link that locked in the settle or // fail. This is then used by the *incoming* link to find the settle // fail in another link's forwarding packages. Source lnwire.ShortChannelID // Height is the remote commitment height that locked in this // Settle/Fail. Height uint64 // Index is the index of the Add with the fwd pkg's SettleFails. // // NOTE: This index is static over the lifetime of a forwarding package. Index uint16 } // SettleFailAcker is a generic interface providing the ability to acknowledge // settle/fail HTLCs stored in forwarding packages. type SettleFailAcker interface { // AckSettleFails atomically updates the settle-fail filters in *other* // channels' forwarding packages. AckSettleFails(tx kvdb.RwTx, settleFailRefs ...SettleFailRef) error } // GlobalFwdPkgReader is an interface used to retrieve the forwarding packages // of any active channel. type GlobalFwdPkgReader interface { // LoadChannelFwdPkgs loads all known forwarding packages for the given // channel. LoadChannelFwdPkgs(tx kvdb.RTx, source lnwire.ShortChannelID) ([]*FwdPkg, error) } // FwdOperator defines the interfaces for managing forwarding packages that are // external to a particular channel. This interface is used by the switch to // read forwarding packages from arbitrary channels, and acknowledge settles and // fails for locally-sourced payments. type FwdOperator interface { // GlobalFwdPkgReader provides read access to all known forwarding // packages GlobalFwdPkgReader // SettleFailAcker grants the ability to acknowledge settles or fails // residing in arbitrary forwarding packages. SettleFailAcker } // SwitchPackager is a concrete implementation of the FwdOperator interface. // A SwitchPackager offers the ability to read any forwarding package, and ack // arbitrary settle and fail HTLCs. type SwitchPackager struct{} // NewSwitchPackager instantiates a new SwitchPackager. func NewSwitchPackager() *SwitchPackager { return &SwitchPackager{} } // AckSettleFails atomically updates the settle-fail filters in *other* // channels' forwarding packages, to mark that the switch has received a settle // or fail residing in the forwarding package of a link. func (*SwitchPackager) AckSettleFails(tx kvdb.RwTx, settleFailRefs ...SettleFailRef) error { return ackSettleFails(tx, settleFailRefs) } // LoadChannelFwdPkgs loads all forwarding packages for a particular channel. func (*SwitchPackager) LoadChannelFwdPkgs(tx kvdb.RTx, source lnwire.ShortChannelID) ([]*FwdPkg, error) { return loadChannelFwdPkgs(tx, source) } // FwdPackager supports all operations required to modify fwd packages, such as // creation, updates, reading, and removal. The interfaces are broken down in // this way to support future delegation of the subinterfaces. type FwdPackager interface { // AddFwdPkg serializes and writes a FwdPkg for this channel at the // remote commitment height included in the forwarding package. AddFwdPkg(tx kvdb.RwTx, fwdPkg *FwdPkg) error // SetFwdFilter looks up the forwarding package at the remote `height` // and sets the `fwdFilter`, marking the Adds for which: // 1) We are not the exit node // 2) Passed all validation // 3) Should be forwarded to the switch immediately after a failure SetFwdFilter(tx kvdb.RwTx, height uint64, fwdFilter *PkgFilter) error // AckAddHtlcs atomically updates the add filters in this channel's // forwarding packages to mark the resolution of an Add that was // received from the remote party. AckAddHtlcs(tx kvdb.RwTx, addRefs ...AddRef) error // SettleFailAcker allows a link to acknowledge settle/fail HTLCs // belonging to other channels. SettleFailAcker // LoadFwdPkgs loads all known forwarding packages owned by this // channel. LoadFwdPkgs(tx kvdb.RTx) ([]*FwdPkg, error) // RemovePkg deletes a forwarding package owned by this channel at // the provided remote `height`. RemovePkg(tx kvdb.RwTx, height uint64) error } // ChannelPackager is used by a channel to manage the lifecycle of its forwarding // packages. The packager is tied to a particular source channel ID, allowing it // to create and edit its own packages. Each packager also has the ability to // remove fail/settle htlcs that correspond to an add contained in one of // source's packages. type ChannelPackager struct { source lnwire.ShortChannelID } // NewChannelPackager creates a new packager for a single channel. func NewChannelPackager(source lnwire.ShortChannelID) *ChannelPackager { return &ChannelPackager{ source: source, } } // AddFwdPkg writes a newly locked in forwarding package to disk. func (*ChannelPackager) AddFwdPkg(tx kvdb.RwTx, fwdPkg *FwdPkg) error { // nolint: dupl fwdPkgBkt, err := tx.CreateTopLevelBucket(fwdPackagesKey) if err != nil { return err } source := makeLogKey(fwdPkg.Source.ToUint64()) sourceBkt, err := fwdPkgBkt.CreateBucketIfNotExists(source[:]) if err != nil { return err } heightKey := makeLogKey(fwdPkg.Height) heightBkt, err := sourceBkt.CreateBucketIfNotExists(heightKey[:]) if err != nil { return err } // Write ADD updates we received at this commit height. addBkt, err := heightBkt.CreateBucketIfNotExists(addBucketKey) if err != nil { return err } // Write SETTLE/FAIL updates we received at this commit height. failSettleBkt, err := heightBkt.CreateBucketIfNotExists(failSettleBucketKey) if err != nil { return err } for i := range fwdPkg.Adds { err = putLogUpdate(addBkt, uint16(i), &fwdPkg.Adds[i]) if err != nil { return err } } // Persist the initialized pkg filter, which will be used to determine // when we can remove this forwarding package from disk. var ackFilterBuf bytes.Buffer if err := fwdPkg.AckFilter.Encode(&ackFilterBuf); err != nil { return err } if err := heightBkt.Put(ackFilterKey, ackFilterBuf.Bytes()); err != nil { return err } for i := range fwdPkg.SettleFails { err = putLogUpdate(failSettleBkt, uint16(i), &fwdPkg.SettleFails[i]) if err != nil { return err } } var settleFailFilterBuf bytes.Buffer err = fwdPkg.SettleFailFilter.Encode(&settleFailFilterBuf) if err != nil { return err } return heightBkt.Put(settleFailFilterKey, settleFailFilterBuf.Bytes()) } // putLogUpdate writes an htlc to the provided `bkt`, using `index` as the key. func putLogUpdate(bkt kvdb.RwBucket, idx uint16, htlc *LogUpdate) error { var b bytes.Buffer if err := serializeLogUpdate(&b, htlc); err != nil { return err } return bkt.Put(uint16Key(idx), b.Bytes()) } // LoadFwdPkgs scans the forwarding log for any packages that haven't been // processed, and returns their deserialized log updates in a map indexed by the // remote commitment height at which the updates were locked in. func (p *ChannelPackager) LoadFwdPkgs(tx kvdb.RTx) ([]*FwdPkg, error) { return loadChannelFwdPkgs(tx, p.source) } // loadChannelFwdPkgs loads all forwarding packages owned by `source`. func loadChannelFwdPkgs(tx kvdb.RTx, source lnwire.ShortChannelID) ([]*FwdPkg, error) { fwdPkgBkt := tx.ReadBucket(fwdPackagesKey) if fwdPkgBkt == nil { return nil, nil } sourceKey := makeLogKey(source.ToUint64()) sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:]) if sourceBkt == nil { return nil, nil } var heights []uint64 if err := sourceBkt.ForEach(func(k, _ []byte) error { if len(k) != 8 { return ErrCorruptedFwdPkg } heights = append(heights, byteOrder.Uint64(k)) return nil }); err != nil { return nil, err } // Load the forwarding package for each retrieved height. fwdPkgs := make([]*FwdPkg, 0, len(heights)) for _, height := range heights { fwdPkg, err := loadFwdPkg(fwdPkgBkt, source, height) if err != nil { return nil, err } fwdPkgs = append(fwdPkgs, fwdPkg) } return fwdPkgs, nil } // loadFwdPkg reads the packager's fwd pkg at a given height, and determines the // appropriate FwdState. func loadFwdPkg(fwdPkgBkt kvdb.RBucket, source lnwire.ShortChannelID, height uint64) (*FwdPkg, error) { sourceKey := makeLogKey(source.ToUint64()) sourceBkt := fwdPkgBkt.NestedReadBucket(sourceKey[:]) if sourceBkt == nil { return nil, ErrCorruptedFwdPkg } heightKey := makeLogKey(height) heightBkt := sourceBkt.NestedReadBucket(heightKey[:]) if heightBkt == nil { return nil, ErrCorruptedFwdPkg } // Load ADDs from disk. addBkt := heightBkt.NestedReadBucket(addBucketKey) if addBkt == nil { return nil, ErrCorruptedFwdPkg } adds, err := loadHtlcs(addBkt) if err != nil { return nil, err } // Load ack filter from disk. ackFilterBytes := heightBkt.Get(ackFilterKey) if ackFilterBytes == nil { return nil, ErrCorruptedFwdPkg } ackFilterReader := bytes.NewReader(ackFilterBytes) ackFilter := &PkgFilter{} if err := ackFilter.Decode(ackFilterReader); err != nil { return nil, err } // Load SETTLE/FAILs from disk. failSettleBkt := heightBkt.NestedReadBucket(failSettleBucketKey) if failSettleBkt == nil { return nil, ErrCorruptedFwdPkg } failSettles, err := loadHtlcs(failSettleBkt) if err != nil { return nil, err } // Load settle fail filter from disk. settleFailFilterBytes := heightBkt.Get(settleFailFilterKey) if settleFailFilterBytes == nil { return nil, ErrCorruptedFwdPkg } settleFailFilterReader := bytes.NewReader(settleFailFilterBytes) settleFailFilter := &PkgFilter{} if err := settleFailFilter.Decode(settleFailFilterReader); err != nil { return nil, err } // Initialize the fwding package, which always starts in the // FwdStateLockedIn. We can determine what state the package was left in // by examining constraints on the information loaded from disk. fwdPkg := &FwdPkg{ Source: source, State: FwdStateLockedIn, Height: height, Adds: adds, AckFilter: ackFilter, SettleFails: failSettles, SettleFailFilter: settleFailFilter, } // Check to see if we have written the set exported filter adds to // disk. If we haven't, processing of this package was never started, or // failed during the last attempt. fwdFilterBytes := heightBkt.Get(fwdFilterKey) if fwdFilterBytes == nil { nAdds := uint16(len(adds)) fwdPkg.FwdFilter = NewPkgFilter(nAdds) return fwdPkg, nil } fwdFilterReader := bytes.NewReader(fwdFilterBytes) fwdPkg.FwdFilter = &PkgFilter{} if err := fwdPkg.FwdFilter.Decode(fwdFilterReader); err != nil { return nil, err } // Otherwise, a complete round of processing was completed, and we // advance the package to FwdStateProcessed. fwdPkg.State = FwdStateProcessed // If every add, settle, and fail has been fully acknowledged, we can // safely set the package's state to FwdStateCompleted, signalling that // it can be garbage collected. if fwdPkg.AckFilter.IsFull() && fwdPkg.SettleFailFilter.IsFull() { fwdPkg.State = FwdStateCompleted } return fwdPkg, nil } // loadHtlcs retrieves all serialized htlcs in a bucket, returning // them in order of the indexes they were written under. func loadHtlcs(bkt kvdb.RBucket) ([]LogUpdate, error) { var htlcs []LogUpdate if err := bkt.ForEach(func(_, v []byte) error { htlc, err := deserializeLogUpdate(bytes.NewReader(v)) if err != nil { return err } htlcs = append(htlcs, *htlc) return nil }); err != nil { return nil, err } return htlcs, nil } // SetFwdFilter writes the set of indexes corresponding to Adds at the // `height` that are to be forwarded to the switch. Calling this method causes // the forwarding package at `height` to be in FwdStateProcessed. We write this // forwarding decision so that we always arrive at the same behavior for HTLCs // leaving this channel. After a restart, we skip validation of these Adds, // since they are assumed to have already been validated, and make the switch or // outgoing link responsible for handling replays. func (p *ChannelPackager) SetFwdFilter(tx kvdb.RwTx, height uint64, fwdFilter *PkgFilter) error { fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey) if fwdPkgBkt == nil { return ErrCorruptedFwdPkg } source := makeLogKey(p.source.ToUint64()) sourceBkt := fwdPkgBkt.NestedReadWriteBucket(source[:]) if sourceBkt == nil { return ErrCorruptedFwdPkg } heightKey := makeLogKey(height) heightBkt := sourceBkt.NestedReadWriteBucket(heightKey[:]) if heightBkt == nil { return ErrCorruptedFwdPkg } // If the fwd filter has already been written, we return early to avoid // modifying the persistent state. forwardedAddsBytes := heightBkt.Get(fwdFilterKey) if forwardedAddsBytes != nil { return nil } // Otherwise we serialize and write the provided fwd filter. var b bytes.Buffer if err := fwdFilter.Encode(&b); err != nil { return err } return heightBkt.Put(fwdFilterKey, b.Bytes()) } // AckAddHtlcs accepts a list of references to add htlcs, and updates the // AckAddFilter of those forwarding packages to indicate that a settle or fail // has been received in response to the add. func (p *ChannelPackager) AckAddHtlcs(tx kvdb.RwTx, addRefs ...AddRef) error { if len(addRefs) == 0 { return nil } fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey) if fwdPkgBkt == nil { return ErrCorruptedFwdPkg } sourceKey := makeLogKey(p.source.ToUint64()) sourceBkt := fwdPkgBkt.NestedReadWriteBucket(sourceKey[:]) if sourceBkt == nil { return ErrCorruptedFwdPkg } // Organize the forward references such that we just get a single slice // of indexes for each unique height. heightDiffs := make(map[uint64][]uint16) for _, addRef := range addRefs { heightDiffs[addRef.Height] = append( heightDiffs[addRef.Height], addRef.Index, ) } // Load each height bucket once and remove all acked htlcs at that // height. for height, indexes := range heightDiffs { err := ackAddHtlcsAtHeight(sourceBkt, height, indexes) if err != nil { return err } } return nil } // ackAddHtlcsAtHeight updates the AddAckFilter of a single forwarding package // with a list of indexes, writing the resulting filter back in its place. func ackAddHtlcsAtHeight(sourceBkt kvdb.RwBucket, height uint64, indexes []uint16) error { heightKey := makeLogKey(height) heightBkt := sourceBkt.NestedReadWriteBucket(heightKey[:]) if heightBkt == nil { // If the height bucket isn't found, this could be because the // forwarding package was already removed. We'll return nil to // signal that the operation is successful, as there is nothing // to ack. return nil } // Load ack filter from disk. ackFilterBytes := heightBkt.Get(ackFilterKey) if ackFilterBytes == nil { return ErrCorruptedFwdPkg } ackFilter := &PkgFilter{} ackFilterReader := bytes.NewReader(ackFilterBytes) if err := ackFilter.Decode(ackFilterReader); err != nil { return err } // Update the ack filter for this height. for _, index := range indexes { ackFilter.Set(index) } // Write the resulting filter to disk. var ackFilterBuf bytes.Buffer if err := ackFilter.Encode(&ackFilterBuf); err != nil { return err } return heightBkt.Put(ackFilterKey, ackFilterBuf.Bytes()) } // AckSettleFails persistently acknowledges settles or fails from a remote forwarding // package. This should only be called after the source of the Add has locked in // the settle/fail, or it becomes otherwise safe to forgo retransmitting the // settle/fail after a restart. func (p *ChannelPackager) AckSettleFails(tx kvdb.RwTx, settleFailRefs ...SettleFailRef) error { return ackSettleFails(tx, settleFailRefs) } // ackSettleFails persistently acknowledges a batch of settle fail references. func ackSettleFails(tx kvdb.RwTx, settleFailRefs []SettleFailRef) error { if len(settleFailRefs) == 0 { return nil } fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey) if fwdPkgBkt == nil { return ErrCorruptedFwdPkg } // Organize the forward references such that we just get a single slice // of indexes for each unique destination-height pair. destHeightDiffs := make(map[lnwire.ShortChannelID]map[uint64][]uint16) for _, settleFailRef := range settleFailRefs { destHeights, ok := destHeightDiffs[settleFailRef.Source] if !ok { destHeights = make(map[uint64][]uint16) destHeightDiffs[settleFailRef.Source] = destHeights } destHeights[settleFailRef.Height] = append( destHeights[settleFailRef.Height], settleFailRef.Index, ) } // With the references organized by destination and height, we now load // each remote bucket, and update the settle fail filter for any // settle/fail htlcs. for dest, destHeights := range destHeightDiffs { destKey := makeLogKey(dest.ToUint64()) destBkt := fwdPkgBkt.NestedReadWriteBucket(destKey[:]) if destBkt == nil { // If the destination bucket is not found, this is // likely the result of the destination channel being // closed and having it's forwarding packages wiped. We // won't treat this as an error, because the response // will no longer be retransmitted internally. continue } for height, indexes := range destHeights { err := ackSettleFailsAtHeight(destBkt, height, indexes) if err != nil { return err } } } return nil } // ackSettleFailsAtHeight given a destination bucket, acks the provided indexes // at particular a height by updating the settle fail filter. func ackSettleFailsAtHeight(destBkt kvdb.RwBucket, height uint64, indexes []uint16) error { heightKey := makeLogKey(height) heightBkt := destBkt.NestedReadWriteBucket(heightKey[:]) if heightBkt == nil { // If the height bucket isn't found, this could be because the // forwarding package was already removed. We'll return nil to // signal that the operation is as there is nothing to ack. return nil } // Load ack filter from disk. settleFailFilterBytes := heightBkt.Get(settleFailFilterKey) if settleFailFilterBytes == nil { return ErrCorruptedFwdPkg } settleFailFilter := &PkgFilter{} settleFailFilterReader := bytes.NewReader(settleFailFilterBytes) if err := settleFailFilter.Decode(settleFailFilterReader); err != nil { return err } // Update the ack filter for this height. for _, index := range indexes { settleFailFilter.Set(index) } // Write the resulting filter to disk. var settleFailFilterBuf bytes.Buffer if err := settleFailFilter.Encode(&settleFailFilterBuf); err != nil { return err } return heightBkt.Put(settleFailFilterKey, settleFailFilterBuf.Bytes()) } // RemovePkg deletes the forwarding package at the given height from the // packager's source bucket. func (p *ChannelPackager) RemovePkg(tx kvdb.RwTx, height uint64) error { fwdPkgBkt := tx.ReadWriteBucket(fwdPackagesKey) if fwdPkgBkt == nil { return nil } sourceBytes := makeLogKey(p.source.ToUint64()) sourceBkt := fwdPkgBkt.NestedReadWriteBucket(sourceBytes[:]) if sourceBkt == nil { return ErrCorruptedFwdPkg } heightKey := makeLogKey(height) return sourceBkt.DeleteNestedBucket(heightKey[:]) } // uint16Key writes the provided 16-bit unsigned integer to a 2-byte slice. func uint16Key(i uint16) []byte { key := make([]byte, 2) byteOrder.PutUint16(key, i) return key } // Compile-time constraint to ensure that ChannelPackager implements the public // FwdPackager interface. var _ FwdPackager = (*ChannelPackager)(nil) // Compile-time constraint to ensure that SwitchPackager implements the public // FwdOperator interface. var _ FwdOperator = (*SwitchPackager)(nil)