nursery_store: adds kindergarten txn finalization

This commit is contained in:
Conner Fromknecht 2017-10-18 06:58:24 -07:00
parent ef81f0064c
commit 0ed5f83dce
No known key found for this signature in database
GPG Key ID: 39DE78FBE6ACB0EF

View File

@ -11,10 +11,90 @@ import (
"github.com/roasbeef/btcd/wire"
)
// Overview of Nursery Store Storage Hierarchy
//
// CHAIN SEGMENTATION
//
// The root directory of a nursery store is bucketed by the chain hash and
// the 'utxn' prefix. This allows multiple utxo nurseries for distinct chains
// to simultaneously use the same channel.DB instance. This is critical for
// providing replay protection and more to isolate chain-specific data in the
// multichain setting.
//
// utxn<chain-hash>/
// |
// | LAST PURGED + FINALIZED HEIGHTS
// |
// | Each nursery store tracks a "last purged height", which records the
// | most recent block height for which the nursery store has purged all
// | state. This value lags behind the best block height for reorg safety,
// | and serves as a starting height for rescans after a restart. It also
// | tracks a "last finalized height", which records the last block height
// | that the nursery attempted to graduate. If a finalized height has
// | kindergarten outputs, the sweep txn for these outputs will be stored in
// | the height bucket. This ensure that the same txid will be used after
// | restarts. Otherwise, the nursery will be unable to recover the txid
// | of kindergarten sweep transaction it has already broadcast.
// |
// ├── last-purged-height-key: <last-purged-height>
// ├── last-finalized-height-key: <last-finalized-height>
// |
// | CHANNEL INDEX
// |
// | The channel index contains a directory for each channel that has a
// | non-zero number of outputs being tracked by the nursery store.
// | Inside each channel directory are files containing serialized spendable
// | outputs that are awaiting some state transition. The name of each file
// | contains the outpoint of the spendable output in the file, and is
// | prefixed with 4-byte state prefix, indicating whether the spendable
// | output is a crib, preschool, or kindergarten, or graduated output. The
// | nursery store supports the ability to enumerate all outputs for a
// | particular channel, which is useful in constructing nursery reports.
// |
// ├── channel-index-key/
// │   ├── <chan-point-1>/ <- CHANNEL BUCKET
// | |   ├── <state-prefix><outpoint-1>: <spendable-output-1>
// | |   └── <state-prefix><outpoint-2>: <spendable-output-2>
// │   ├── <chan-point-2>/
// | |   └── <state-prefix><outpoint-3>: <spendable-output-3>
// │   └── <chan-point-3>/
// |    ├── <state-prefix><outpoint-4>: <spendable-output-4>
// |    └── <state-prefix><outpoint-5>: <spendable-output-5>
// |
// | HEIGHT INDEX
// |
// | The height index contains a directory for each height at which the
// | nursery still has scheduled actions. If an output is a crib or
// | kindergarten output, it will have an associated entry in the height
// | index. Inside a particular height directory, the structure is similar
// | to that of the channel index, containing multiple channel directories,
// | each of which contains subdirectories named with a prefixed outpoint
// | belonging to the channel. Enumerating these combinations yields a
// | relative file path:
// | e.g. <chan-point-3>/<prefix><outpoint-2>/
// | that can be queried in the channel index to retrieve the serialized
// | output. If a height bucket is less than or equal to the current last
// | finalized height and has a non-zero number of kindergarten outputs, a
// | height bucket will also contain the finalized kindergarten sweep txn
// | under the "finalized-kndr-txn" key.
// |
// └── height-index-key/
//    ├── <height-1>/ <- HEIGHT BUCKET
// |   ├── <chan-point-3>/ <- HEIGHT-CHANNEL BUCKET
// | |    ├── <state-prefix><outpoint-4>: "" <- PREFIXED OUTPOINT
// | |    └── <state-prefix><outpoint-5>: ""
// |   ├── <chan-point-2>/
// | |    └── <state-prefix><outpoint-3>: ""
//    | └── finalized-kndr-txn: "" | <kndr-sweep-tnx>
//    └── <height-2>/
//    └── <chan-point-1>/
//    └── <state-prefix><outpoint-1>: ""
//    └── <state-prefix><outpoint-2>: ""
// NurseryStore abstracts the persistent storage layer for the utxo nursery.
// Concretely, it stores commitment and htlc outputs that until any time-bounded
// constraints have fully matured. The store exposes methods for enumerating
// its contents, and persisting state transitions detected by the utxo nursery.
// Concretely, it stores commitment and htlc outputs until any time-bounded
// constraints have fully matured. The store exposes methods for enumerating its
// contents, and persisting state transitions detected by the utxo nursery.
type NurseryStore interface {
// Incubate registers a commitment output and a slice of htlc outputs to
@ -35,32 +115,39 @@ type NurseryStore interface {
PreschoolToKinder(*kidOutput) error
// GraduateKinder accepts a slice of kidOutputs from the kindergarten
// bucket, and removes their corresponding entries from the height and
// channel indexes. If this method detects that all outputs for a
// particular contract have been incubated, it returns the channel
// points that are ready to be marked as fully closed.
GraduateKinder([]kidOutput) error
// FinalizeHeight accepts a block height as a parameter and purges its
// persistent state for all outputs at that height. During a restart,
// the utxo nursery will begin it's recovery procedure from the next
// height that has yet to be finalized. This block height should lag
// beyond the best height for this chain as a measure of reorg
// protection.
FinalizeHeight(height uint32) error
// LastFinalizedHeight returns the last block height for which the
// nursery store has purged all persistent state.
LastFinalizedHeight() (uint32, error)
// FetchClass returns a list of babyOutputs in the crib bucket whose
// CLTV delay expires at the provided block height.
FetchClass(height uint32) ([]kidOutput, []babyOutput, error)
// bucket and marks them graduated. It also removes their corresponding
// entries from the height and channel indexes, pruning any intermediate
// buckets that become empty.
GraduateKinder(height uint32, kndrOutputs []kidOutput) error
// FetchPreschools returns a list of all outputs currently stored in the
// preschool bucket.
FetchPreschools() ([]kidOutput, 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
// will also returns the finalized kindergarten sweep txn.
FetchClass(height uint32) (*wire.MsgTx, []kidOutput, []babyOutput, error)
// FinalizeKinder accepts a block height and the kindergarten sweep txn
// computed for this height. Upon startup, we will rebroadcast any
// finalized kindergarten txns instead of signing a new txn, as this
// result in a different txid from a preceding broadcast.
FinalizeKinder(height uint32, tx *wire.MsgTx) error
// LastFinalizedHeight returns the last block height for which the
// nursery store finalized a kindergarten class.
LastFinalizedHeight() (uint32, error)
// PurgeHeight deletes specified the height bucket if it exists, and
// records it as that last purged height.
PurgeHeight(height uint32) error
// LastPurgedHeight returns the last block height for which the nursery
// store has purged all persistent state.
LastPurgedHeight() (uint32, error)
// ForChanOutputs iterates over all outputs being incubated for a
// particular channel point. This method accepts a callback that allows
// the caller to process each key-value pair. The key will be a prefixed
@ -73,10 +160,68 @@ type NurseryStore interface {
IsMatureChannel(*wire.OutPoint) (bool, error)
// RemoveChannel channel erases all entries from the channel bucket for
// the provided channel point.
// the provided channel point, this method should only be called if
// IsMatureChannel indicates the channel is ready for removal.
RemoveChannel(*wire.OutPoint) error
}
var (
// utxnChainPrefix is used to prefix a particular chain hash and create
// the root-level, chain-segmented bucket for each nursery store.
utxnChainPrefix = []byte("utxn")
// lastFinalizedHeightKey is a static key used to locate nursery store's
// last finalized height.
lastFinalizedHeightKey = []byte("last-finalized-height")
// lastPurgedHeightKey is a static key used to retrieve the height of
// the last bucket that was purged.
lastPurgedHeightKey = []byte("last-purged-height")
// channelIndexKey is a static key used to lookup the bucket containing
// all of the nursery's active channels.
channelIndexKey = []byte("channel-index")
// channelIndexKey is a static key used to retrieve a directory
// containing all heights for which the nursery will need to take
// action.
heightIndexKey = []byte("height-index")
// finalizedKndrTxnKey is a static key that can be used to locate a
// finalized kindergarten sweep txn.
finalizedKndrTxnKey = []byte("finalized-kndr-txn")
)
// Defines the state prefixes that will be used to persistently track an
// output's progress through the nursery.
// NOTE: Each state prefix MUST be exactly 4 bytes in length, the nursery logic
// depends on the ability to create keys for a different state by overwriting
// an existing state prefix.
var (
// cribPrefix is the state prefix given to htlc outputs waiting for
// their first-stage, absolute locktime to elapse.
cribPrefix = []byte("crib")
// psclPrefix is the state prefix given to commitment outputs awaiting
// the confirmation of the commitment transaction, as this solidifies
// the absolute height at which they can be spent.
psclPrefix = []byte("pscl")
// kndrPrefix is the state prefix given to all CSV delayed outputs,
// either from the commitment transaction, or a stage-one htlc
// transaction, whose maturity height has solidified. Outputs marked in
// this state are in their final stage of incubation withn the nursery,
// and will be swept into the wallet after waiting out the relative
// timelock.
kndrPrefix = []byte("kndr")
// gradPrefix is the state prefix given to all outputs that have been
// completely incubated. Once all outputs have been marked as graduated,
// this serves as a persistent marker that the nursery should mark the
// channel fully closed in the channeldb.
gradPrefix = []byte("grad")
)
// prefixChainKey creates the root level keys for the nursery store. The keys
// are comprised of a nursery-specific prefix and the intended chain hash that
// this nursery store will be used for. This allows multiple nursery stores to
@ -117,117 +262,6 @@ func prefixOutputKey(statePrefix []byte,
return pfxOutputBuffer.Bytes(), nil
}
var (
// utxnChainPrefix is used to prefix a particular chain hash and create
// the root-level, chain-segmented bucket for each nursery store.
utxnChainPrefix = []byte("utxn")
// lastFinalizedHeightKey is a static key used to locate nursery store's
// last finalized height.
lastFinalizedHeightKey = []byte("last-finalized-height")
// channelIndexKey is a static key used to lookup the bucket containing
// all of the nursery's active channels.
channelIndexKey = []byte("channel-index")
// channelIndexKey is a static key used to retrieve a directory
// containing all heights for which the nursery will need to take
// action.
heightIndexKey = []byte("height-index")
// cribPrefix is the state prefix given to htlc outputs waiting for
// their first-stage, absolute locktime to elapse.
cribPrefix = []byte("crib")
// psclPrefix is the state prefix given to commitment outputs awaiting
// the // confirmation of the commitment transaction, as this solidifies
// the absolute height at which they can be spent.
psclPrefix = []byte("pscl")
// kndrPrefix is the state prefix given to all CSV delayed outputs,
// either from the commitment transaction, or a stage-one htlc
// transaction, whose maturity height has solidified. Outputs marked in
// this state are in their final stage of incubation withn the nursery,
// and will be swept into the wallet after waiting out the relative
// timelock.
kndrPrefix = []byte("kndr")
// gradPrefix is the state prefix given to all outputs that have been
// completely incubated. Once all outputs have been marked as graduated,
// this serves as a persistent marker that the nursery should mark the
// channel fully closed in the channeldb.
gradPrefix = []byte("grad")
)
// Overview of Nursery Store Storage Hierarchy
//
// CHAIN SEGMENTATION
//
// The root directory of a nursery store is bucketed by the chain hash and
// the 'utxn' prefix. This allows multiple utxo nurseries for distinct chains
// to simultaneously use the same channel.DB instance. This is critical for
// providing replay protection and more to isolate chain-specific data in the
// multichain setting.
//
// utxn<chain-hash>/
// |
// | LAST FINALIZED HEIGHT
// |
// | Each nursery store tracks a "last finalized height", which records the
// | most recent block height for which the nursery store has purged all
// | state. This value lags behind the best block height for reorg safety,
// | and serves as a starting height for rescans after a restart.
// |
// ├── last-finalized-height-key: <last-finalized-height>
// |
// | CHANNEL INDEX
// |
// | The channel index contains a directory for each channel that has a
// | non-zero number of outputs being tracked by the nursery store.
// | Inside each channel directory are files containing serialized spendable
// | outputs that are awaiting some state transition. The name of each file
// | contains the outpoint of the spendable output in the file, and is
// | prefixed with 4-byte state prefix, indicating whether the spendable
// | output is a crib, preschool, or kindergarten output. The nursery store
// | supports the ability to enumerate all outputs for a particular channel,
// | which is useful in constructing nursery reports.
// |
// ├── channel-index-key/
// │   ├── <chan-point-1>/ <- CHANNEL BUCKET
// | |   ├── <state-prefix><outpoint-1>: <spendable-output-1>
// | |   └── <state-prefix><outpoint-2>: <spendable-output-2>
// │   ├── <chan-point-2>/
// | |   └── <state-prefix><outpoint-3>: <spendable-output-3>
// │   └── <chan-point-3>/
// |    ├── <state-prefix><outpoint-4>: <spendable-output-4>
// |    └── <state-prefix><outpoint-5>: <spendable-output-5>
// |
// | HEIGHT INDEX
// |
// | The height index contains a directory for each height at which the
// | nursery still has uncompleted actions. If an output is a crib or
// | kindergarten output, it will have an associated entry in the height
// | index. Inside a particular height directory, the structure is similar
// | to that of the channel index, containing multiple channel directories,
// | each of which contains subdirectories named with a prefixed outpoint
// | belonging to the channel. Enumerating these combinations yields a
// | relative file path:
// | e.g. <chan-point-3>/<prefix><outpoint-2>/
// | that can be queried in the channel index to retrieve the serialized
// | output.
// |
// └── height-index-key/
//    ├── <height-1>/ <- HEIGHT BUCKET
// |   └── <chan-point-3>/ <- HEIGHT-CHANNEL BUCKET
// | |    ├── <state-prefix><outpoint-4>: "" <- PREFIXED OUTPOINT
// | |    └── <state-prefix><outpoint-5>: ""
// |   └── <chan-point-2>/
// |    └── <state-prefix><outpoint-3>: ""
//    └── <height-2>/
//    └── <chan-point-1>/
//    └── <state-prefix><outpoint-1>: ""
//    └── <state-prefix><outpoint-2>: ""
// nurseryStore is a concrete instantiation of a NurseryStore that is backed by
// a channeldb.DB instance.
type nurseryStore struct {
@ -258,8 +292,8 @@ func newNurseryStore(chainHash *chainhash.Hash,
}, nil
}
// Incubate initiates the incubation process for the CSV-delayed commitment
// output and any number of CLTV-delayed htlc outputs.
// Incubate persists the beginning of the incubation process for the CSV-delayed
// commitment output and a list of two-stage htlc outputs.
func (ns *nurseryStore) Incubate(kid *kidOutput, babies []babyOutput) error {
return ns.db.Update(func(tx *bolt.Tx) error {
// Store commitment output in preschool bucket if not nil.
@ -436,12 +470,12 @@ func (ns *nurseryStore) PreschoolToKinder(kid *kidOutput) error {
// GraduateKinder accepts a list of kidOutputs in the kindergarten bucket and
// marks them as graduated. This method also removes their corresponding entries
// from the height and channel indexes corresponding to their kindergarten
// status.
func (ns *nurseryStore) GraduateKinder(kids []kidOutput) error {
// status. It also ensures that the finalized kindergarten sweep txn is removed
// for this height.
func (ns *nurseryStore) GraduateKinder(height uint32, kids []kidOutput) error {
if err := ns.db.Update(func(tx *bolt.Tx) error {
for _, kid := range kids {
confHeight := kid.ConfHeight()
outpoint := kid.OutPoint()
chanPoint := kid.OriginChanPoint()
@ -454,7 +488,7 @@ func (ns *nurseryStore) GraduateKinder(kids []kidOutput) error {
}
// Remove the grad output's entry in the height index.
err = ns.removeOutputFromHeight(tx, confHeight, chanPoint,
err = ns.removeOutputFromHeight(tx, height, chanPoint,
pfxOutputKey)
if err != nil {
return err
@ -479,14 +513,23 @@ func (ns *nurseryStore) GraduateKinder(kids []kidOutput) error {
}
// Insert serialized output into channel bucket using
// kindergarten-prefixed key.
// graduate-prefixed key.
err = chanBucket.Put(pfxOutputKey, gradBuffer.Bytes())
if err != nil {
return err
}
}
return nil
hghtBucket := ns.getHeightBucket(tx, height)
if hghtBucket == nil {
return nil
}
// Since all kindergarten outputs at a particular height are
// swept in a single txn, we can now safely delete the finalized
// txn, since it has already been broadcast and confirmed.
return hghtBucket.Delete(finalizedKndrTxnKey)
}); err != nil {
return err
}
@ -494,32 +537,54 @@ func (ns *nurseryStore) GraduateKinder(kids []kidOutput) error {
return nil
}
// FinalizeHeight accepts a block height as a parameter and purges its
// FinalizeKinder accepts a block height as a parameter and purges its
// persistent state for all outputs at that height. During a restart, the utxo
// nursery will begin it's recovery procedure from the next height that has
// yet to be finalized.
func (ns *nurseryStore) FinalizeHeight(height uint32) error {
func (ns *nurseryStore) FinalizeKinder(height uint32,
finalTx *wire.MsgTx) error {
return ns.db.Update(func(tx *bolt.Tx) error {
return ns.finalizeKinder(tx, height, finalTx)
})
}
// PurgeHeight accepts a block height as a parameter and purges its persistent
// state for all outputs at that height. During a restart, the utxo nursery will
// begin it's recovery procedure from the next height that has yet to be
// finalized.
func (ns *nurseryStore) PurgeHeight(height uint32) error {
return ns.db.Update(func(tx *bolt.Tx) error {
if err := ns.deleteHeightBucket(tx, height); err != nil {
return err
}
return ns.putLastFinalizedHeight(tx, height)
return ns.putLastPurgedHeight(tx, height)
})
}
// FetchClass returns a list of babyOutputs in the crib bucket whose CLTV
// delay expires at the provided block height.
// FetchClass returns a list of the kindergarten and crib outputs whose timeouts
// are expiring
func (ns *nurseryStore) FetchClass(
height uint32) ([]kidOutput, []babyOutput, error) {
height uint32) (*wire.MsgTx, []kidOutput, []babyOutput, error) {
// Construct list of all crib and kindergarten outputs that need TLC at
// the provided block height.
// Construct list of all crib and kindergarten outputs that need to be
// processed at the provided block height.
var finalTx *wire.MsgTx
var kids []kidOutput
var babies []babyOutput
if err := ns.db.View(func(tx *bolt.Tx) error {
var err error
finalTx, err = ns.getFinalizedTxn(tx, height)
if err != nil {
return err
}
// Append each crib output to our list of babyOutputs.
if err := ns.forEachHeightPrefix(tx, cribPrefix, height,
if err = ns.forEachHeightPrefix(tx, cribPrefix, height,
func(buf []byte) error {
// We will attempt to deserialize all outputs
@ -560,10 +625,10 @@ func (ns *nurseryStore) FetchClass(
})
}); err != nil {
return nil, nil, err
return nil, nil, nil, err
}
return kids, babies, nil
return finalTx, kids, babies, nil
}
// FetchPreschools returns a list of all outputs currently stored in the
@ -649,7 +714,7 @@ func (ns *nurseryStore) FetchPreschools() ([]kidOutput, error) {
// process each key-value pair. The key will be a prefixed outpoint, and the
// value will be the serialized bytes for an output, whose type should be
// inferred from the key's prefix.
// NOTE: The callback should be not modify the provided byte slices and is
// NOTE: The callback should not modify the provided byte slices and is
// preferably non-blocking.
func (ns *nurseryStore) ForChanOutputs(chanPoint *wire.OutPoint,
callback func([]byte, []byte) error) error {
@ -672,9 +737,7 @@ func (ns *nurseryStore) IsMatureChannel(chanPoint *wire.OutPoint) (bool, error)
// prefix.
return ns.forChanOutputs(tx, chanPoint,
func(pfxKey, _ []byte) error {
if string(pfxKey[:4]) != string(gradPrefix) {
utxnLog.Infof("Found non-graduated "+
"output: %x", pfxKey)
if !bytes.HasPrefix(pfxKey, gradPrefix) {
return errImmatureChannel
}
return nil
@ -713,10 +776,8 @@ func (ns *nurseryStore) RemoveChannel(chanPoint *wire.OutPoint) error {
}
chanBytes := chanBuffer.Bytes()
utxnLog.Infof("Pruning and removing channel: %v", chanPoint)
err := ns.forChanOutputs(tx, chanPoint, func(k, v []byte) error {
if string(k[:4]) != string(gradPrefix) {
if !bytes.HasPrefix(k, gradPrefix) {
return errors.New("expected grad output")
}
@ -764,12 +825,26 @@ func (ns *nurseryStore) LastFinalizedHeight() (uint32, error) {
return lastFinalizedHeight, err
}
// LastPurgedHeight returns the last block height for which the nursery store
// has purged all persistent state. This occurs after a fixed interval for reorg
// safety.
func (ns *nurseryStore) LastPurgedHeight() (uint32, error) {
var lastPurgedHeight uint32
err := ns.db.View(func(tx *bolt.Tx) error {
var err error
lastPurgedHeight, err = ns.getLastPurgedHeight(tx)
return err
})
return lastPurgedHeight, err
}
// Helper Methods
// enterCrib accepts a new htlc output that the nursery will incubate through
// its two-stage process of sweeping funds back to the user's wallet. These
// outputs are persisted in the nursery store's crib bucket, and will be
// revisited after the output's CLTV has expired.
// outputs are persisted in the nursery store in the crib state, and will be
// revisited after the first-stage output's CLTV has expired.
func (ns *nurseryStore) enterCrib(tx *bolt.Tx, baby *babyOutput) error {
// First, retrieve or create the channel bucket corresponding to the
// baby output's origin channel point.
@ -828,7 +903,7 @@ func (ns *nurseryStore) enterPreschool(tx *bolt.Tx, kid *kidOutput) error {
return err
}
// Since the babyOutput is being inserted into the preschool bucket, we
// Since the kidOutput is being inserted into the preschool bucket, we
// create a key that prefixes its outpoint with the preschool prefix.
pfxOutputKey, err := prefixOutputKey(psclPrefix, kid.OutPoint())
if err != nil {
@ -965,17 +1040,15 @@ func (ns *nurseryStore) getHeightBucket(tx *bolt.Tx,
return hghtBucket
}
// deleteHeightBucket ensures that the height bucket at the provided index is
// purgeHeightBucket ensures that the height bucket at the provided index is
// purged from the nursery store.
func (ns *nurseryStore) deleteHeightBucket(tx *bolt.Tx, height uint32) error {
func (ns *nurseryStore) purgeHeightBucket(tx *bolt.Tx, height uint32) error {
// Ensure that the height bucket already exists.
_, hghtIndex, hghtBucket := ns.getHeightBucketPath(tx, height)
if hghtBucket == nil {
return nil
}
utxnLog.Infof("Deleting height bucket %d", height)
// Serialize the provided height, as this will form the name of the
// bucket.
var heightBytes [4]byte
@ -1057,8 +1130,10 @@ func (ns *nurseryStore) forEachHeightPrefix(tx *bolt.Tx, prefix []byte,
// buckets identified by a channel point, thus we first create list of
// channels contained in this height bucket.
var channelsAtHeight [][]byte
if err := hghtBucket.ForEach(func(chanBytes, _ []byte) error {
channelsAtHeight = append(channelsAtHeight, chanBytes)
if err := hghtBucket.ForEach(func(chanBytes, v []byte) error {
if v == nil {
channelsAtHeight = append(channelsAtHeight, chanBytes)
}
return nil
}); err != nil {
return err
@ -1166,10 +1241,19 @@ func (ns *nurseryStore) getLastFinalizedHeight(tx *bolt.Tx) (uint32, error) {
return byteOrder.Uint32(heightBytes), nil
}
// pubLastFinalizedHeight is a helper method that writes the provided height
// under the last finalized height key.
func (ns *nurseryStore) putLastFinalizedHeight(tx *bolt.Tx,
height uint32) error {
// finalizeKinder records a finalized kingergarten sweep txn to the given height
// bucket. It also updates the nursery store's last finalized height, so that we
// do not finalize the same height twice. If the finalized txn is nil, i.e. if
// the height has no kindergarten outputs, the height will be marked as
// finalized, and we skip the process of writing the txn. When the class is
// loaded, a nil value will be returned if no txn has been written to a
// finalized height bucket.
func (ns *nurseryStore) finalizeKinder(tx *bolt.Tx, height uint32,
finalTx *wire.MsgTx) error {
// TODO(conner) ensure height is greater that current finalized height.
// 1. Write the last finalized height to the chain bucket.
// Ensure that the chain bucket for this nursery store exists.
chainBucket, err := tx.CreateBucketIfNotExists(ns.pfxChainKey)
@ -1182,18 +1266,102 @@ func (ns *nurseryStore) putLastFinalizedHeight(tx *bolt.Tx,
var lastHeightBytes [4]byte
byteOrder.PutUint32(lastHeightBytes[:], height)
return chainBucket.Put(lastFinalizedHeightKey, lastHeightBytes[:])
err = chainBucket.Put(lastFinalizedHeightKey, lastHeightBytes[:])
if err != nil {
return err
}
// 2. Write the finalized txn in the appropriate height bucket.
// If there is no finalized txn, we have nothing to do.
if finalTx == nil {
return nil
}
// Otherwise serialize the finalized txn and write it to the height
// bucket.
hghtBucket := ns.getHeightBucket(tx, height)
if hghtBucket == nil {
return nil
}
var finalTxnBuf bytes.Buffer
if err := finalTx.Serialize(&finalTxnBuf); err != nil {
return err
}
return hghtBucket.Put(finalizedKndrTxnKey, finalTxnBuf.Bytes())
}
var (
// ErrBucketDoesNotExist signals that a bucket has already been removed,
// or was never created.
ErrBucketDoesNotExist = errors.New("bucket does not exist")
// getFinalizedTxn retrieves the finalized kindergarten sweep txn at the given
// height, returning nil if one was not found.
func (ns *nurseryStore) getFinalizedTxn(tx *bolt.Tx,
height uint32) (*wire.MsgTx, error) {
// ErrBucketNotEmpty signals that an attempt to prune a particular
// bucket failed because it still has active outputs.
ErrBucketNotEmpty = errors.New("bucket is not empty, cannot be pruned")
)
hghtBucket := ns.getHeightBucket(tx, height)
if hghtBucket == nil {
// No class to finalize.
return nil, nil
}
finalTxBytes := hghtBucket.Get(finalizedKndrTxnKey)
if finalTxBytes == nil {
// No finalized txn for this height.
return nil, nil
}
// Otherwise, deserialize and return the finalized transaction.
txn := &wire.MsgTx{}
if err := txn.Deserialize(bytes.NewReader(finalTxBytes)); err != nil {
return nil, err
}
return txn, nil
}
// getLastPurgedHeight is a helper method that retrieves the last height for
// which the database purged its persistent state.
func (ns *nurseryStore) getLastPurgedHeight(tx *bolt.Tx) (uint32, error) {
// Retrieve the chain bucket associated with the given nursery store.
chainBucket := tx.Bucket(ns.pfxChainKey)
if chainBucket == nil {
return 0, nil
}
// Lookup the last finalized height in the top-level chain bucket.
heightBytes := chainBucket.Get(lastPurgedHeightKey)
// If the resulting bytes are not sized like a uint32, then we have
// never finalized, so we return 0.
if len(heightBytes) != 4 {
return 0, nil
}
// Otherwise, parse the bytes and return the last finalized height.
return byteOrder.Uint32(heightBytes), nil
}
// pubLastPurgedHeight is a helper method that writes the provided height under
// the last purged height key.
func (ns *nurseryStore) putLastPurgedHeight(tx *bolt.Tx, height uint32) error {
// Ensure that the chain bucket for this nursery store exists.
chainBucket, err := tx.CreateBucketIfNotExists(ns.pfxChainKey)
if err != nil {
return err
}
// Serialize the provided last-finalized height, and store it in the
// top-level chain bucket for this nursery store.
var lastHeightBytes [4]byte
byteOrder.PutUint32(lastHeightBytes[:], height)
return chainBucket.Put(lastPurgedHeightKey, lastHeightBytes[:])
}
// ErrBucketNotEmpty signals that an attempt to prune a particular
// bucket failed because it still has active outputs.
var ErrBucketNotEmpty = errors.New("bucket is not empty, cannot be pruned")
// removeOutputFromHeight will delete the given output from the specified
// height-channel bucket, and attempt to prune the upstream directories if they
@ -1208,13 +1376,10 @@ func (ns *nurseryStore) removeOutputFromHeight(tx *bolt.Tx, height uint32,
return nil
}
// Try to delete the prefixed output key if it still exists. The output
// may have already been removed after confirmation, but a final pass is
// done when removing a channel as well.
if hghtChanBucket.Get(pfxKey) != nil {
if err := hghtChanBucket.Delete(pfxKey); err != nil {
return err
}
// Try to delete the prefixed output from the target height-channel
// bucket.
if err := hghtChanBucket.Delete(pfxKey); err != nil {
return err
}
// Retrieve the height bucket that contains the height-channel bucket.
@ -1251,22 +1416,26 @@ func (ns *nurseryStore) removeOutputFromHeight(tx *bolt.Tx, height uint32,
// pruneHeight removes the height bucket at the provided height if and only if
// all active outputs at this height have been removed from their respective
// height-channel buckets.
// height-channel buckets. The returned boolean value indicated whether or not
// this invocation successfully pruned the height bucket.
func (ns *nurseryStore) pruneHeight(tx *bolt.Tx, height uint32) (bool, error) {
// Fetch the existing height index and height bucket.
_, hghtIndex, hghtBucket := ns.getHeightBucketPath(tx, height)
if hghtBucket == nil {
return false, nil
}
utxnLog.Infof("pruning height %d", height)
// Iterate over all channels stored at this block height. We will
// attempt to remove each one if they are empty, keeping track of the
// number of height-channel buckets that still have active outputs.
if err := hghtBucket.ForEach(func(chanBytes, _ []byte) error {
if err := hghtBucket.ForEach(func(chanBytes, v []byte) error {
// Skip the finalized txn key.
if v != nil {
return nil
}
// Attempt to each height-channel bucket from the height bucket
// located above.
hghtChanBucket := hghtBucket.Bucket(chanBytes)
if hghtChanBucket == nil {
return errors.New("unable to find height-channel bucket")