utxonursery: adds babyOutput and CsvSpendableOutput

This commit is contained in:
Conner Fromknecht 2017-09-28 18:26:42 -07:00
parent 6253419ce5
commit 47f40014f5
No known key found for this signature in database
GPG Key ID: 39DE78FBE6ACB0EF

View File

@ -176,13 +176,13 @@ func (u *utxoNursery) reloadPreschool(heightHint uint32) error {
} }
return psclBucket.ForEach(func(outputBytes, kidBytes []byte) error { return psclBucket.ForEach(func(outputBytes, kidBytes []byte) error {
psclOutput, err := deserializeKidOutput(bytes.NewBuffer(kidBytes)) var psclOutput kidOutput
err := psclOutput.Decode(bytes.NewBuffer(kidBytes))
if err != nil { if err != nil {
return err return err
} }
outpoint := psclOutput.outPoint sourceTxid := psclOutput.OutPoint().Hash
sourceTxid := outpoint.Hash
confChan, err := u.notifier.RegisterConfirmationsNtfn( confChan, err := u.notifier.RegisterConfirmationsNtfn(
&sourceTxid, 1, heightHint, &sourceTxid, 1, heightHint,
@ -192,7 +192,7 @@ func (u *utxoNursery) reloadPreschool(heightHint uint32) error {
} }
utxnLog.Infof("Preschool outpoint %v re-registered for confirmation "+ utxnLog.Infof("Preschool outpoint %v re-registered for confirmation "+
"notification.", psclOutput.outPoint) "notification.", psclOutput.OutPoint())
go psclOutput.waitForPromotion(u.db, confChan) go psclOutput.waitForPromotion(u.db, confChan)
return nil return nil
}) })
@ -250,28 +250,6 @@ func (u *utxoNursery) Stop() error {
return nil return nil
} }
// kidOutput represents an output that's waiting for a required blockheight
// before its funds will be available to be moved into the user's wallet. The
// struct includes a WitnessGenerator closure which will be used to generate
// the witness required to sweep the output once it's mature.
//
// TODO(roasbeef): rename to immatureOutput?
type kidOutput struct {
originChanPoint wire.OutPoint
amt btcutil.Amount
outPoint wire.OutPoint
// TODO(roasbeef): using block timeouts everywhere currently, will need
// to modify logic later to account for MTP based timeouts.
blocksToMaturity uint32
confHeight uint32
signDescriptor *lnwallet.SignDescriptor
witnessType lnwallet.WitnessType
witnessFunc lnwallet.WitnessGenerator
}
// incubationRequest is a request to the utxoNursery to incubate a set of // incubationRequest is a request to the utxoNursery to incubate a set of
// outputs until their mature, finally sweeping them into the wallet once // outputs until their mature, finally sweeping them into the wallet once
// available. // available.
@ -289,17 +267,15 @@ func (u *utxoNursery) IncubateOutputs(closeSummary *lnwallet.ForceCloseSummary)
// that case the SignDescriptor would be nil and we would not have that // that case the SignDescriptor would be nil and we would not have that
// output to incubate. // output to incubate.
if closeSummary.SelfOutputSignDesc != nil { if closeSummary.SelfOutputSignDesc != nil {
outputAmt := btcutil.Amount(closeSummary.SelfOutputSignDesc.Output.Value) selfOutput := makeKidOutput(
selfOutput := &kidOutput{ &closeSummary.SelfOutpoint,
originChanPoint: closeSummary.ChanPoint, &closeSummary.ChanPoint,
amt: outputAmt, closeSummary.SelfOutputMaturity,
outPoint: closeSummary.SelfOutpoint, lnwallet.CommitmentTimeLock,
blocksToMaturity: closeSummary.SelfOutputMaturity, closeSummary.SelfOutputSignDesc,
signDescriptor: closeSummary.SelfOutputSignDesc, )
witnessType: lnwallet.CommitmentTimeLock,
}
incReq.outputs = append(incReq.outputs, selfOutput) incReq.outputs = append(incReq.outputs, &selfOutput)
} }
// If there are no outputs to incubate, there is nothing to send to the // If there are no outputs to incubate, there is nothing to send to the
@ -336,11 +312,11 @@ out:
// We'll skip any zero value'd outputs as this // We'll skip any zero value'd outputs as this
// indicates we don't have a settled balance // indicates we don't have a settled balance
// within the commitment transaction. // within the commitment transaction.
if output.amt == 0 { if output.Amount() == 0 {
continue continue
} }
sourceTxid := output.outPoint.Hash sourceTxid := output.OutPoint().Hash
if err := output.enterPreschool(u.db); err != nil { if err := output.enterPreschool(u.db); err != nil {
utxnLog.Errorf("unable to add kidOutput to preschool: %v, %v ", utxnLog.Errorf("unable to add kidOutput to preschool: %v, %v ",
@ -500,25 +476,25 @@ func (u *utxoNursery) NurseryReport(chanPoint *wire.OutPoint) (*contractMaturity
// With the proper set of bytes received, we'll deserialize the // With the proper set of bytes received, we'll deserialize the
// information for this immature output. // information for this immature output.
immatureOutput, err := deserializeKidOutput(outputReader) var immatureOutput kidOutput
if err != nil { if err := immatureOutput.Decode(outputReader); err != nil {
return err return err
} }
// TODO(roasbeef): should actually be list of outputs // TODO(roasbeef): should actually be list of outputs
report = &contractMaturityReport{ report = &contractMaturityReport{
chanPoint: *chanPoint, chanPoint: *chanPoint,
limboBalance: immatureOutput.amt, limboBalance: immatureOutput.Amount(),
maturityRequirement: immatureOutput.blocksToMaturity, maturityRequirement: immatureOutput.BlocksToMaturity(),
} }
// If the confirmation height is set, then this means the // If the confirmation height is set, then this means the
// contract has been confirmed, and we know the final maturity // contract has been confirmed, and we know the final maturity
// height. // height.
if immatureOutput.confHeight != 0 { if immatureOutput.ConfHeight() != 0 {
report.confirmationHeight = immatureOutput.confHeight report.confirmationHeight = immatureOutput.ConfHeight()
report.maturityHeight = (immatureOutput.blocksToMaturity + report.maturityHeight = (immatureOutput.BlocksToMaturity() +
immatureOutput.confHeight) immatureOutput.ConfHeight())
} }
return nil return nil
@ -547,11 +523,11 @@ func (k *kidOutput) enterPreschool(db *channeldb.DB) error {
// Once we have the buckets we can insert the raw bytes of the // Once we have the buckets we can insert the raw bytes of the
// immature outpoint into the preschool bucket. // immature outpoint into the preschool bucket.
var outpointBytes bytes.Buffer var outpointBytes bytes.Buffer
if err := writeOutpoint(&outpointBytes, &k.outPoint); err != nil { if err := writeOutpoint(&outpointBytes, k.OutPoint()); err != nil {
return err return err
} }
var kidBytes bytes.Buffer var kidBytes bytes.Buffer
if err := serializeKidOutput(&kidBytes, k); err != nil { if err := k.Encode(&kidBytes); err != nil {
return err return err
} }
err = psclBucket.Put(outpointBytes.Bytes(), kidBytes.Bytes()) err = psclBucket.Put(outpointBytes.Bytes(), kidBytes.Bytes())
@ -563,7 +539,7 @@ func (k *kidOutput) enterPreschool(db *channeldb.DB) error {
// track all the immature outpoints for a particular channel's // track all the immature outpoints for a particular channel's
// chanPoint. // chanPoint.
var b bytes.Buffer var b bytes.Buffer
err = writeOutpoint(&b, &k.originChanPoint) err = writeOutpoint(&b, k.OriginChanPoint())
if err != nil { if err != nil {
return err return err
} }
@ -573,7 +549,7 @@ func (k *kidOutput) enterPreschool(db *channeldb.DB) error {
} }
utxnLog.Infof("Outpoint %v now in preschool, waiting for "+ utxnLog.Infof("Outpoint %v now in preschool, waiting for "+
"initial confirmation", k.outPoint) "initial confirmation", k.OutPoint())
return nil return nil
}) })
@ -589,14 +565,14 @@ func (k *kidOutput) waitForPromotion(db *channeldb.DB, confChan *chainntnfs.Conf
txConfirmation, ok := <-confChan.Confirmed txConfirmation, ok := <-confChan.Confirmed
if !ok { if !ok {
utxnLog.Errorf("notification chan "+ utxnLog.Errorf("notification chan "+
"closed, can't advance output %v", k.outPoint) "closed, can't advance output %v", k.OutPoint())
return return
} }
utxnLog.Infof("Outpoint %v confirmed in block %v moving to kindergarten", utxnLog.Infof("Outpoint %v confirmed in block %v moving to kindergarten",
k.outPoint, txConfirmation.BlockHeight) k.OutPoint(), txConfirmation.BlockHeight)
k.confHeight = txConfirmation.BlockHeight k.SetConfHeight(txConfirmation.BlockHeight)
// The following block deletes a kidOutput from the preschool database // The following block deletes a kidOutput from the preschool database
// bucket and adds it to the kindergarten database bucket which is // bucket and adds it to the kindergarten database bucket which is
@ -604,7 +580,7 @@ func (k *kidOutput) waitForPromotion(db *channeldb.DB, confChan *chainntnfs.Conf
// array form prior to database insertion. // array form prior to database insertion.
err := db.Update(func(tx *bolt.Tx) error { err := db.Update(func(tx *bolt.Tx) error {
var originPoint bytes.Buffer var originPoint bytes.Buffer
if err := writeOutpoint(&originPoint, &k.originChanPoint); err != nil { if err := writeOutpoint(&originPoint, k.OriginChanPoint()); err != nil {
return err return err
} }
@ -621,17 +597,17 @@ func (k *kidOutput) waitForPromotion(db *channeldb.DB, confChan *chainntnfs.Conf
// along in the maturity pipeline we first delete the entry // along in the maturity pipeline we first delete the entry
// from the preschool bucket, as well as the secondary index. // from the preschool bucket, as well as the secondary index.
var outpointBytes bytes.Buffer var outpointBytes bytes.Buffer
if err := writeOutpoint(&outpointBytes, &k.outPoint); err != nil { if err := writeOutpoint(&outpointBytes, k.OutPoint()); err != nil {
return err return err
} }
if err := psclBucket.Delete(outpointBytes.Bytes()); err != nil { if err := psclBucket.Delete(outpointBytes.Bytes()); err != nil {
utxnLog.Errorf("unable to delete kindergarten output from "+ utxnLog.Errorf("unable to delete kindergarten output from "+
"preschool bucket: %v", k.outPoint) "preschool bucket: %v", k.OutPoint())
return err return err
} }
if err := psclIndex.Delete(originPoint.Bytes()); err != nil { if err := psclIndex.Delete(originPoint.Bytes()); err != nil {
utxnLog.Errorf("unable to delete kindergarten output from "+ utxnLog.Errorf("unable to delete kindergarten output from "+
"preschool index: %v", k.outPoint) "preschool index: %v", k.OutPoint())
return err return err
} }
@ -642,7 +618,7 @@ func (k *kidOutput) waitForPromotion(db *channeldb.DB, confChan *chainntnfs.Conf
return err return err
} }
maturityHeight := k.confHeight + k.blocksToMaturity maturityHeight := k.ConfHeight() + k.BlocksToMaturity()
heightBytes := make([]byte, 4) heightBytes := make([]byte, 4)
byteOrder.PutUint32(heightBytes, maturityHeight) byteOrder.PutUint32(heightBytes, maturityHeight)
@ -660,7 +636,7 @@ func (k *kidOutput) waitForPromotion(db *channeldb.DB, confChan *chainntnfs.Conf
outputOffset := len(existingOutputs) outputOffset := len(existingOutputs)
b := bytes.NewBuffer(existingOutputs) b := bytes.NewBuffer(existingOutputs)
if err := serializeKidOutput(b, k); err != nil { if err := k.Encode(b); err != nil {
return err return err
} }
if err := kgtnBucket.Put(heightBytes, b.Bytes()); err != nil { if err := kgtnBucket.Put(heightBytes, b.Bytes()); err != nil {
@ -684,8 +660,8 @@ func (k *kidOutput) waitForPromotion(db *channeldb.DB, confChan *chainntnfs.Conf
} }
utxnLog.Infof("Outpoint %v now in kindergarten, will mature "+ utxnLog.Infof("Outpoint %v now in kindergarten, will mature "+
"at height %v (delay of %v)", k.outPoint, "at height %v (delay of %v)", k.OutPoint(),
maturityHeight, k.blocksToMaturity) maturityHeight, k.BlocksToMaturity())
return nil return nil
}) })
if err != nil { if err != nil {
@ -722,7 +698,7 @@ func (u *utxoNursery) graduateKindergarten(blockHeight uint32) error {
// each of the immature outputs, we'll mark them as being fully // each of the immature outputs, we'll mark them as being fully
// closed within the database. // closed within the database.
for _, closedChan := range kgtnOutputs { for _, closedChan := range kgtnOutputs {
err := u.db.MarkChanFullyClosed(&closedChan.originChanPoint) err := u.db.MarkChanFullyClosed(closedChan.OriginChanPoint())
if err != nil { if err != nil {
return err return err
} }
@ -786,7 +762,7 @@ func fetchGraduatingOutputs(db *channeldb.DB, wallet *lnwallet.LightningWallet,
// output or not. // output or not.
for _, kgtnOutput := range kgtnOutputs { for _, kgtnOutput := range kgtnOutputs {
kgtnOutput.witnessFunc = kgtnOutput.witnessType.GenWitnessFunc( kgtnOutput.witnessFunc = kgtnOutput.witnessType.GenWitnessFunc(
wallet.Cfg.Signer, kgtnOutput.signDescriptor) wallet.Cfg.Signer, kgtnOutput.SignDesc())
} }
utxnLog.Infof("New block: height=%v, sweeping %v mature outputs", utxnLog.Infof("New block: height=%v, sweeping %v mature outputs",
@ -842,7 +818,7 @@ func createSweepTx(wallet *lnwallet.LightningWallet,
var totalSum btcutil.Amount var totalSum btcutil.Amount
for _, o := range matureOutputs { for _, o := range matureOutputs {
totalSum += o.amt totalSum += o.Amount()
} }
sweepTx := wire.NewMsgTx(2) sweepTx := wire.NewMsgTx(2)
@ -852,9 +828,9 @@ func createSweepTx(wallet *lnwallet.LightningWallet,
}) })
for _, utxo := range matureOutputs { for _, utxo := range matureOutputs {
sweepTx.AddTxIn(&wire.TxIn{ sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: utxo.outPoint, PreviousOutPoint: *utxo.OutPoint(),
// TODO(roasbeef): assumes pure block delays // TODO(roasbeef): assumes pure block delays
Sequence: utxo.blocksToMaturity, Sequence: utxo.BlocksToMaturity(),
}) })
} }
@ -915,7 +891,7 @@ func deleteGraduatedOutputs(db *channeldb.DB, deleteHeight uint32) error {
} }
for _, sweptOutput := range sweptOutputs { for _, sweptOutput := range sweptOutputs {
var chanPoint bytes.Buffer var chanPoint bytes.Buffer
err := writeOutpoint(&chanPoint, &sweptOutput.originChanPoint) err := writeOutpoint(&chanPoint, sweptOutput.OriginChanPoint())
if err != nil { if err != nil {
return err return err
} }
@ -964,101 +940,223 @@ func deserializeKidList(r io.Reader) ([]*kidOutput, error) {
var kidOutputs []*kidOutput var kidOutputs []*kidOutput
for { for {
kidOutput, err := deserializeKidOutput(r) var kid = &kidOutput{}
if err != nil { if err := kid.Decode(r); err != nil {
if err == io.EOF { if err == io.EOF {
break break
} else { } else {
return nil, err return nil, err
} }
} }
kidOutputs = append(kidOutputs, kidOutput) kidOutputs = append(kidOutputs, kid)
} }
return kidOutputs, nil return kidOutputs, nil
} }
// serializeKidOutput converts a KidOutput struct into a form // CsvSpendableOutput is a SpendableOutput that contains all of the information
// suitable for on-disk database storage. Note that the signDescriptor // necessary to construct, sign, and sweep an output locked with a CSV delay.
// struct field is included so that the output's witness can be generated type CsvSpendableOutput interface {
// by createSweepTx() when the output becomes spendable. SpendableOutput
func serializeKidOutput(w io.Writer, kid *kidOutput) error {
var scratch [8]byte // ConfHeight returns the height at which this output was confirmed.
byteOrder.PutUint64(scratch[:], uint64(kid.amt)) // A zero value indicates that the output has not been confirmed.
ConfHeight() uint32
// SetConfHeight marks the height at which the output is confirmed in
// the chain.
SetConfHeight(height uint32)
// BlocksToMaturity returns the relative timelock, as a number of
// blocks, that must be built on top of the confirmation height before
// the output can be spent.
BlocksToMaturity() uint32
// OriginChanPoint returns the outpoint of the channel from which this
// output is derived.
OriginChanPoint() *wire.OutPoint
}
// babyOutput is an HTLC output that is in the earliest stage of upbringing.
// Each babyOutput carries a presigned timeout transction, which should be
// broadcast at the appropriate CLTV expiry, and its future kidOutput self. If
// all goes well, and the timeout transaction is successfully confirmed, the
// the now-mature kidOutput will be unwrapped and continue its journey through
// the nursery.
type babyOutput struct {
kidOutput
expiry uint32
timeoutTx *wire.MsgTx
}
// makeBabyOutput constructs baby output the wraps a future kidOutput. The
// provided sign descriptors and witness types will be used once the output
// reaches the delay and claim stage.
func makeBabyOutput(outpoint, originChanPoint *wire.OutPoint,
blocksToMaturity uint32, witnessType lnwallet.WitnessType,
htlcResolution *lnwallet.OutgoingHtlcResolution) babyOutput {
kid := makeKidOutput(outpoint, originChanPoint,
blocksToMaturity, witnessType,
&htlcResolution.SweepSignDesc)
return babyOutput{
kidOutput: kid,
expiry: htlcResolution.Expiry,
timeoutTx: htlcResolution.SignedTimeoutTx,
}
}
// Encode writes the baby output to the given io.Writer.
func (bo *babyOutput) Encode(w io.Writer) error {
var scratch [4]byte
byteOrder.PutUint32(scratch[:], bo.expiry)
if _, err := w.Write(scratch[:]); err != nil { if _, err := w.Write(scratch[:]); err != nil {
return err return err
} }
if err := writeOutpoint(w, &kid.outPoint); err != nil { if err := bo.timeoutTx.Serialize(w); err != nil {
return err
}
if err := writeOutpoint(w, &kid.originChanPoint); err != nil {
return err return err
} }
byteOrder.PutUint32(scratch[:4], kid.blocksToMaturity) return bo.kidOutput.Encode(w)
}
// Decode reconstructs a baby output using the provide io.Reader.
func (bo *babyOutput) Decode(r io.Reader) error {
var scratch [4]byte
if _, err := r.Read(scratch[:]); err != nil {
return err
}
bo.expiry = byteOrder.Uint32(scratch[:])
bo.timeoutTx = new(wire.MsgTx)
if err := bo.timeoutTx.Deserialize(r); err != nil {
return err
}
return bo.kidOutput.Decode(r)
}
// kidOutput represents an output that's waiting for a required blockheight
// before its funds will be available to be moved into the user's wallet. The
// struct includes a WitnessGenerator closure which will be used to generate
// the witness required to sweep the output once it's mature.
//
// TODO(roasbeef): rename to immatureOutput?
type kidOutput struct {
breachedOutput
originChanPoint wire.OutPoint
// TODO(roasbeef): using block timeouts everywhere currently, will need
// to modify logic later to account for MTP based timeouts.
blocksToMaturity uint32
confHeight uint32
}
func makeKidOutput(outpoint, originChanPoint *wire.OutPoint,
blocksToMaturity uint32, witnessType lnwallet.WitnessType,
signDescriptor *lnwallet.SignDescriptor) kidOutput {
return kidOutput{
breachedOutput: makeBreachedOutput(
outpoint, witnessType, signDescriptor,
),
originChanPoint: *originChanPoint,
blocksToMaturity: blocksToMaturity,
}
}
func (k *kidOutput) OriginChanPoint() *wire.OutPoint {
return &k.originChanPoint
}
func (k *kidOutput) BlocksToMaturity() uint32 {
return k.blocksToMaturity
}
func (k *kidOutput) SetConfHeight(height uint32) {
k.confHeight = height
}
func (k *kidOutput) ConfHeight() uint32 {
return k.confHeight
}
// Encode converts a KidOutput struct into a form suitable for on-disk database
// storage. Note that the signDescriptor struct field is included so that the
// output's witness can be generated by createSweepTx() when the output becomes
// spendable.
func (k *kidOutput) Encode(w io.Writer) error {
var scratch [8]byte
byteOrder.PutUint64(scratch[:], uint64(k.Amount()))
if _, err := w.Write(scratch[:]); err != nil {
return err
}
if err := writeOutpoint(w, k.OutPoint()); err != nil {
return err
}
if err := writeOutpoint(w, k.OriginChanPoint()); err != nil {
return err
}
byteOrder.PutUint32(scratch[:4], k.BlocksToMaturity())
if _, err := w.Write(scratch[:4]); err != nil { if _, err := w.Write(scratch[:4]); err != nil {
return err return err
} }
byteOrder.PutUint32(scratch[:4], kid.confHeight) byteOrder.PutUint32(scratch[:4], k.ConfHeight())
if _, err := w.Write(scratch[:4]); err != nil { if _, err := w.Write(scratch[:4]); err != nil {
return err return err
} }
byteOrder.PutUint16(scratch[:2], uint16(kid.witnessType)) byteOrder.PutUint16(scratch[:2], uint16(k.WitnessType()))
if _, err := w.Write(scratch[:2]); err != nil { if _, err := w.Write(scratch[:2]); err != nil {
return err return err
} }
return lnwallet.WriteSignDescriptor(w, kid.signDescriptor) return lnwallet.WriteSignDescriptor(w, k.SignDesc())
} }
// deserializeKidOutput takes a byte array representation of a kidOutput // Decode takes a byte array representation of a kidOutput and converts it to an
// and converts it to an struct. Note that the witnessFunc method isn't added // struct. Note that the witnessFunc method isn't added during deserialization
// during deserialization and must be added later based on the value of the // and must be added later based on the value of the witnessType field.
// witnessType field. func (k *kidOutput) Decode(r io.Reader) error {
func deserializeKidOutput(r io.Reader) (*kidOutput, error) { var scratch [8]byte
scratch := make([]byte, 8)
kid := &kidOutput{}
if _, err := r.Read(scratch[:]); err != nil { if _, err := r.Read(scratch[:]); err != nil {
return nil, err return err
} }
kid.amt = btcutil.Amount(byteOrder.Uint64(scratch[:])) k.amt = btcutil.Amount(byteOrder.Uint64(scratch[:]))
err := readOutpoint(io.LimitReader(r, 40), &kid.outPoint) if err := readOutpoint(io.LimitReader(r, 40), &k.outpoint); err != nil {
if err != nil { return err
return nil, err
} }
err = readOutpoint(io.LimitReader(r, 40), &kid.originChanPoint) err := readOutpoint(io.LimitReader(r, 40), &k.originChanPoint)
if err != nil { if err != nil {
return nil, err return err
} }
if _, err := r.Read(scratch[:4]); err != nil { if _, err := r.Read(scratch[:4]); err != nil {
return nil, err return err
} }
kid.blocksToMaturity = byteOrder.Uint32(scratch[:4]) k.blocksToMaturity = byteOrder.Uint32(scratch[:4])
if _, err := r.Read(scratch[:4]); err != nil { if _, err := r.Read(scratch[:4]); err != nil {
return nil, err return err
} }
kid.confHeight = byteOrder.Uint32(scratch[:4]) k.confHeight = byteOrder.Uint32(scratch[:4])
if _, err := r.Read(scratch[:2]); err != nil { if _, err := r.Read(scratch[:2]); err != nil {
return nil, err return err
} }
kid.witnessType = lnwallet.WitnessType(byteOrder.Uint16(scratch[:2])) k.witnessType = lnwallet.WitnessType(byteOrder.Uint16(scratch[:2]))
kid.signDescriptor = &lnwallet.SignDescriptor{} return lnwallet.ReadSignDescriptor(r, &k.signDesc)
if err := lnwallet.ReadSignDescriptor(r, kid.signDescriptor); err != nil {
return nil, err
}
return kid, nil
} }
// TODO(bvu): copied from channeldb, remove repetition // TODO(bvu): copied from channeldb, remove repetition