Skip to content
Closed
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ flakehunter: build

flake-unit:
@$(call print, "Flake hunting unit tests.")
$(UNIT) -count=1
while [ $$? -eq 0 ]; do /bin/sh -c "$(UNIT) -count=1"; done
GOTRACEBACK=all $(UNIT) -count=1
while [ $$? -eq 0 ]; do /bin/sh -c "GOTRACEBACK=all $(UNIT) -count=1"; done

# ======
# TRAVIS
Expand Down
242 changes: 234 additions & 8 deletions nursery_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ type NurseryStore interface {
// kidOutput's CSV delay.
CribToKinder(*babyOutput) error

// CribToRemoteSpend marks an output as spend by the remote party.
CribToRemoteSpend(*babyOutput) error

// KidToRemoteSpend marks an output as spend by the remote party.
KidToRemoteSpend(*kidOutput) error

// PreschoolToKinder atomically moves a kidOutput from the preschool
// bucket to the kindergarten bucket. This transition should be
// executed after receiving confirmation of the preschool output.
Expand All @@ -128,6 +134,14 @@ type NurseryStore interface {
// the preschool bucket.
FetchPreschools() ([]kidOutput, error)

// FetchKinder returns a list of all outputs currently stored in
// the kindergarten bucket.
FetchKinder() ([]kidOutput, error)

// FetchCribs returns a list of all outputs currently stored in the
// cribs bucket.
FetchCribs() ([]babyOutput, error)

// FetchClass returns a list of kindergarten and crib outputs whose
// timelocks expire at the given height. If the kindergarten class at
// this height hash been finalized previously, via FinalizeKinder, it
Expand Down Expand Up @@ -231,6 +245,8 @@ var (
// this serves as a persistent marker that the nursery should mark the
// channel fully closed in the channeldb.
gradPrefix = []byte("grad")

spndPrefix = []byte("spnd")
)

// prefixChainKey creates the root level keys for the nursery store. The keys
Expand Down Expand Up @@ -412,6 +428,132 @@ func (ns *nurseryStore) CribToKinder(bby *babyOutput) error {
})
}

func (ns *nurseryStore) CribToRemoteSpend(bby *babyOutput) error {
return ns.db.Update(func(tx *bolt.Tx) error {

// First, retrieve or create the channel bucket corresponding to
// the baby output's origin channel point.
chanPoint := bby.OriginChanPoint()
chanBucket, err := ns.createChannelBucket(tx, chanPoint)
if err != nil {
return err
}

// The babyOutput should currently be stored in the crib bucket.
// So, we create a key that prefixes the babyOutput's outpoint
// with the crib prefix, allowing us to reference it in the
// store.
pfxOutputKey, err := prefixOutputKey(cribPrefix, bby.OutPoint())
if err != nil {
return err
}

// Since the babyOutput is being moved to the spend bucket, we
// remove the entry from the channel bucket under the
// crib-prefixed outpoint key.
if err := chanBucket.Delete(pfxOutputKey); err != nil {
return err
}

// Remove the crib output's entry in the height index.
err = ns.removeOutputFromHeight(tx, bby.expiry, chanPoint,
pfxOutputKey)
if err != nil {
return err
}

// Since we are moving this output from the crib bucket to the
// spend bucket, we overwrite the existing prefix of this key
// with the spend prefix.
copy(pfxOutputKey, spndPrefix)

// Now, serialize babyOutput's encapsulated kidOutput such that
// it can be written to the channel bucket under the new
// spend-prefixed key.
var kidBuffer bytes.Buffer
if err := bby.kidOutput.Encode(&kidBuffer); err != nil {
return err
}
kidBytes := kidBuffer.Bytes()

// Persist the serialized kidOutput under the spend-prefixed
// outpoint key.
if err := chanBucket.Put(pfxOutputKey, kidBytes); err != nil {
return err
}

utxnLog.Tracef("Transitioning (crib -> spnd) output for "+
"chan_point=%v", chanPoint)

return nil
})
}

func (ns *nurseryStore) KidToRemoteSpend(kid *kidOutput) error {
return ns.db.Update(func(tx *bolt.Tx) error {

// First, retrieve or create the channel bucket corresponding to
// the baby output's origin channel point.
chanPoint := kid.OriginChanPoint()
chanBucket, err := ns.createChannelBucket(tx, chanPoint)
if err != nil {
return err
}

// The kidOutput should currently be stored in the kndr bucket.
// So, we create a key that prefixes the kidOutput's outpoint
// with the crib prefix, allowing us to reference it in the
// store.
pfxOutputKey, err := prefixOutputKey(kndrPrefix, kid.OutPoint())
if err != nil {
return err
}

// Since the kidOutput is being moved to the spend bucket, we
// remove the entry from the channel bucket under the
// crib-prefixed outpoint key.
if err := chanBucket.Delete(pfxOutputKey); err != nil {
return err
}

// Remove the crib output's entry in the height index.
//
// TODO(joostjager): Here we assume no late graduation into kid
// stage has happened! Probably ok, because channel_arb holds
// back until commit tx is confirmed.
err = ns.removeOutputFromHeight(tx, kid.absoluteMaturity,
chanPoint, pfxOutputKey)
if err != nil {
return err
}

// Since we are moving this output from the crib bucket to the
// spend bucket, we overwrite the existing prefix of this key
// with the spend prefix.
copy(pfxOutputKey, spndPrefix)

// Now, serialize babyOutput's encapsulated kidOutput such that
// it can be written to the channel bucket under the new
// spend-prefixed key.
var kidBuffer bytes.Buffer
if err := kid.Encode(&kidBuffer); err != nil {
return err
}
kidBytes := kidBuffer.Bytes()

// Persist the serialized kidOutput under the spend-prefixed
// outpoint key.
if err := chanBucket.Put(pfxOutputKey, kidBytes); err != nil {
return err
}

utxnLog.Tracef("Transitioning (kid -> spnd) output for "+
"chan_point=%v", chanPoint)

return nil
})
}

// PreschoolToKinder atomically moves a kidOutput from the preschool bucket to
// the kindergarten bucket. This transition should be executed after receiving
// confirmation of the preschool output's commitment transaction.
Expand Down Expand Up @@ -494,7 +636,7 @@ func (ns *nurseryStore) PreschoolToKinder(kid *kidOutput) error {
maturityHeight = lastGradHeight + 1
}

utxnLog.Infof("Transitioning (crib -> kid) output for "+
utxnLog.Infof("Transitioning (preschool -> kid) output for "+
"chan_point=%v at height_index=%v", chanPoint,
maturityHeight)

Expand Down Expand Up @@ -692,6 +834,16 @@ func (ns *nurseryStore) FetchClass(
// FetchPreschools returns a list of all outputs currently stored in the
// preschool bucket.
func (ns *nurseryStore) FetchPreschools() ([]kidOutput, error) {
return ns.fetchKidsByPrefix(psclPrefix)
}

// FetchKinder returns a list of all outputs currently stored in the
// kindergarten bucket.
func (ns *nurseryStore) FetchKinder() ([]kidOutput, error) {
return ns.fetchKidsByPrefix(kndrPrefix)
}

func (ns *nurseryStore) fetchKidsByPrefix(prefix []byte) ([]kidOutput, error) {
var kids []kidOutput
if err := ns.db.View(func(tx *bolt.Tx) error {

Expand Down Expand Up @@ -731,26 +883,26 @@ func (ns *nurseryStore) FetchPreschools() ([]kidOutput, error) {
}

// All of the outputs of interest will start with the
// "pscl" prefix. So, we will perform a prefix scan of
// specified prefix. So, we will perform a prefix scan of
// the channel bucket to efficiently enumerate all the
// desired outputs.
c := chanBucket.Cursor()
for k, v := c.Seek(psclPrefix); bytes.HasPrefix(
k, psclPrefix); k, v = c.Next() {
for k, v := c.Seek(prefix); bytes.HasPrefix(
k, prefix); k, v = c.Next() {

// Deserialize each output as a kidOutput, since
// this should have been the type that was
// serialized when it was written to disk.
var psclOutput kidOutput
psclReader := bytes.NewReader(v)
err := psclOutput.Decode(psclReader)
var kidOutput kidOutput
kidReader := bytes.NewReader(v)
err := kidOutput.Decode(kidReader)
if err != nil {
return err
}

// Add the deserialized output to our list of
// preschool outputs.
kids = append(kids, psclOutput)
kids = append(kids, kidOutput)
}
}

Expand All @@ -762,6 +914,79 @@ func (ns *nurseryStore) FetchPreschools() ([]kidOutput, error) {
return kids, nil
}

// FetchPreschools returns a list of all outputs currently stored in the
// preschool bucket.
func (ns *nurseryStore) FetchCribs() ([]babyOutput, error) {
var babies []babyOutput
if err := ns.db.View(func(tx *bolt.Tx) error {

// Retrieve the existing chain bucket for this nursery store.
chainBucket := tx.Bucket(ns.pfxChainKey)
if chainBucket == nil {
return nil
}

// Load the existing channel index from the chain bucket.
chanIndex := chainBucket.Bucket(channelIndexKey)
if chanIndex == nil {
return nil
}

// Construct a list of all channels in the channel index that
// are currently being tracked by the nursery store.
var activeChannels [][]byte
if err := chanIndex.ForEach(func(chanBytes, _ []byte) error {
activeChannels = append(activeChannels, chanBytes)
return nil
}); err != nil {
return err
}

// Iterate over all of the accumulated channels, and do a prefix
// scan inside of each channel bucket. Each output found that
// has a preschool prefix will be deserialized into a kidOutput,
// and added to our list of preschool outputs to return to the
// caller.
for _, chanBytes := range activeChannels {
// Retrieve the channel bucket associated with this
// channel.
chanBucket := chanIndex.Bucket(chanBytes)
if chanBucket == nil {
continue
}

// All of the outputs of interest will start with the
// "pscl" prefix. So, we will perform a prefix scan of
// the channel bucket to efficiently enumerate all the
// desired outputs.
c := chanBucket.Cursor()
for k, v := c.Seek(cribPrefix); bytes.HasPrefix(
k, cribPrefix); k, v = c.Next() {

// Deserialize each output as a kidOutput, since
// this should have been the type that was
// serialized when it was written to disk.
var cribOutput babyOutput
cribReader := bytes.NewReader(v)
err := cribOutput.Decode(cribReader)
if err != nil {
return err
}

// Add the deserialized output to our list of
// preschool outputs.
babies = append(babies, cribOutput)
}
}

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

return babies, nil
}

// HeightsBelowOrEqual returns a slice of all non-empty heights in the height
// index at or below the provided upper bound.
func (ns *nurseryStore) HeightsBelowOrEqual(height uint32) ([]uint32, error) {
Expand Down Expand Up @@ -1457,6 +1682,7 @@ func (ns *nurseryStore) getLastGraduatedHeight(tx *bolt.Tx) (uint32, error) {
// the last graduated height key.
func (ns *nurseryStore) putLastGraduatedHeight(tx *bolt.Tx, height uint32) error {

utxnLog.Infof("Log last graduated height at %v", height)
// Ensure that the chain bucket for this nursery store exists.
chainBucket, err := tx.CreateBucketIfNotExists(ns.pfxChainKey)
if err != nil {
Expand Down
35 changes: 24 additions & 11 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -630,20 +630,33 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB, cc *chainControl,
outHtlcRes *lnwallet.OutgoingHtlcResolution,
inHtlcRes *lnwallet.IncomingHtlcResolution) error {

var (
inRes []lnwallet.IncomingHtlcResolution
outRes []lnwallet.OutgoingHtlcResolution
)
if inHtlcRes != nil {
inRes = append(inRes, *inHtlcRes)
// TODO(joostjager): Propagate function split further
// up. To prevent adding three function typed fields in
// the config struct, replace by nursery typed field.
// But before this can be done, nursery needs to be put
// in its own package to fix dependencies.
if commitRes == nil &&
outHtlcRes == nil && inHtlcRes != nil {

return s.utxoNursery.IncubateIncomingHtlcOutput(
chanPoint, *inHtlcRes)
}
if outHtlcRes != nil {
outRes = append(outRes, *outHtlcRes)

if commitRes == nil &&
outHtlcRes != nil && inHtlcRes == nil {

return s.utxoNursery.IncubateOutgoingHtlcOutput(
chanPoint, *outHtlcRes)
}

return s.utxoNursery.IncubateOutputs(
chanPoint, commitRes, outRes, inRes,
)
if commitRes != nil &&
outHtlcRes == nil && inHtlcRes == nil {

return s.utxoNursery.IncubateCommitOutput(
chanPoint, commitRes)
}

panic("invalid combination of inputs")
},
PreimageDB: s.witnessBeacon,
Notifier: cc.chainNotifier,
Expand Down
Loading