Merge pull request #361 from cfromknecht/baby-outputs

Adds second layer HTLC output types to UtxoNursery
This commit is contained in:
Olaoluwa Osuntokun 2017-10-05 18:27:05 -07:00 committed by GitHub
commit 3368b238c4
2 changed files with 366 additions and 154 deletions

@ -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,230 @@ 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 {
// expiry is the absolute block height at which the timeoutTx should be
// broadcast to the network.
expiry uint32
// timeoutTx is a fully-signed transaction that, upon confirmation,
// transitions the htlc into the delay+claim stage.
timeoutTx *wire.MsgTx
// kidOutput represents the CSV output to be swept after the timeoutTx has
// been broadcast and confirmed.
kidOutput
}
// makeBabyOutput constructs a 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 provided 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
@ -1126,3 +1231,8 @@ func readTxOut(r io.Reader, txo *wire.TxOut) error {
return nil return nil
} }
// Compile-time constraint to ensure kidOutput and babyOutpt implement the
// CsvSpendableOutput interface.
var _ CsvSpendableOutput = (*kidOutput)(nil)
var _ CsvSpendableOutput = (*babyOutput)(nil)

@ -4,6 +4,7 @@ package main
import ( import (
"bytes" "bytes"
"fmt"
"reflect" "reflect"
"testing" "testing"
@ -167,48 +168,131 @@ var (
kidOutputs = []kidOutput{ kidOutputs = []kidOutput{
{ {
breachedOutput: breachedOutput{
amt: btcutil.Amount(13e7),
outpoint: outPoints[0],
witnessType: lnwallet.CommitmentTimeLock,
},
originChanPoint: outPoints[1], originChanPoint: outPoints[1],
amt: btcutil.Amount(13e7),
outPoint: outPoints[0],
blocksToMaturity: uint32(100), blocksToMaturity: uint32(100),
witnessType: lnwallet.CommitmentTimeLock,
confHeight: uint32(1770001), confHeight: uint32(1770001),
}, },
{ {
breachedOutput: breachedOutput{
amt: btcutil.Amount(24e7),
outpoint: outPoints[1],
witnessType: lnwallet.CommitmentTimeLock,
},
originChanPoint: outPoints[0], originChanPoint: outPoints[0],
amt: btcutil.Amount(24e7),
outPoint: outPoints[1],
blocksToMaturity: uint32(50), blocksToMaturity: uint32(50),
witnessType: lnwallet.CommitmentTimeLock,
confHeight: uint32(22342321), confHeight: uint32(22342321),
}, },
{ {
breachedOutput: breachedOutput{
amt: btcutil.Amount(2e5),
outpoint: outPoints[2],
witnessType: lnwallet.CommitmentTimeLock,
},
originChanPoint: outPoints[2], originChanPoint: outPoints[2],
amt: btcutil.Amount(2e5),
outPoint: outPoints[2],
blocksToMaturity: uint32(12), blocksToMaturity: uint32(12),
witnessType: lnwallet.CommitmentTimeLock,
confHeight: uint32(34241), confHeight: uint32(34241),
}, },
} }
babyOutputs = []babyOutput{
{
kidOutput: kidOutputs[0],
expiry: 3829,
timeoutTx: timeoutTx,
},
{
kidOutput: kidOutputs[1],
expiry: 85903,
timeoutTx: timeoutTx,
},
{
kidOutput: kidOutputs[2],
expiry: 4,
timeoutTx: timeoutTx,
},
}
// Dummy timeout tx used to test serialization, borrowed from btcd
// msgtx_test
timeoutTx = &wire.MsgTx{
Version: 1,
TxIn: []*wire.TxIn{
{
PreviousOutPoint: wire.OutPoint{
Hash: chainhash.Hash{
0xa5, 0x33, 0x52, 0xd5, 0x13, 0x57, 0x66, 0xf0,
0x30, 0x76, 0x59, 0x74, 0x18, 0x26, 0x3d, 0xa2,
0xd9, 0xc9, 0x58, 0x31, 0x59, 0x68, 0xfe, 0xa8,
0x23, 0x52, 0x94, 0x67, 0x48, 0x1f, 0xf9, 0xcd,
},
Index: 19,
},
SignatureScript: []byte{},
Witness: [][]byte{
{ // 70-byte signature
0x30, 0x43, 0x02, 0x1f, 0x4d, 0x23, 0x81, 0xdc,
0x97, 0xf1, 0x82, 0xab, 0xd8, 0x18, 0x5f, 0x51,
0x75, 0x30, 0x18, 0x52, 0x32, 0x12, 0xf5, 0xdd,
0xc0, 0x7c, 0xc4, 0xe6, 0x3a, 0x8d, 0xc0, 0x36,
0x58, 0xda, 0x19, 0x02, 0x20, 0x60, 0x8b, 0x5c,
0x4d, 0x92, 0xb8, 0x6b, 0x6d, 0xe7, 0xd7, 0x8e,
0xf2, 0x3a, 0x2f, 0xa7, 0x35, 0xbc, 0xb5, 0x9b,
0x91, 0x4a, 0x48, 0xb0, 0xe1, 0x87, 0xc5, 0xe7,
0x56, 0x9a, 0x18, 0x19, 0x70, 0x01,
},
{ // 33-byte serialize pub key
0x03, 0x07, 0xea, 0xd0, 0x84, 0x80, 0x7e, 0xb7,
0x63, 0x46, 0xdf, 0x69, 0x77, 0x00, 0x0c, 0x89,
0x39, 0x2f, 0x45, 0xc7, 0x64, 0x25, 0xb2, 0x61,
0x81, 0xf5, 0x21, 0xd7, 0xf3, 0x70, 0x06, 0x6a,
0x8f,
},
},
Sequence: 0xffffffff,
},
},
TxOut: []*wire.TxOut{
{
Value: 395019,
PkScript: []byte{ // p2wkh output
0x00, // Version 0 witness program
0x14, // OP_DATA_20
0x9d, 0xda, 0xc6, 0xf3, 0x9d, 0x51, 0xe0, 0x39,
0x8e, 0x53, 0x2a, 0x22, 0xc4, 0x1b, 0xa1, 0x89,
0x40, 0x6a, 0x85, 0x23, // 20-byte pub key hash
},
},
},
}
) )
func TestAddSerializedKidsToList(t *testing.T) { func init() {
var b bytes.Buffer // Finish initializing our test vectors by parsing the desired public keys and
// properly populating the sign descriptors of all baby and kid outputs.
for i := 0; i < 3; i++ { for i := range signDescriptors {
kid := &kidOutputs[i]
descriptor := &signDescriptors[i]
pk, err := btcec.ParsePubKey(keys[i], btcec.S256()) pk, err := btcec.ParsePubKey(keys[i], btcec.S256())
if err != nil { if err != nil {
t.Fatalf("unable to parse pub key: %v", keys[i]) panic(fmt.Sprintf("unable to parse pub key during init: %v", err))
} }
descriptor.PubKey = pk signDescriptors[i].PubKey = pk
kid.signDescriptor = descriptor
if err := serializeKidOutput(&b, &kidOutputs[i]); err != nil { kidOutputs[i].signDesc = signDescriptors[i]
babyOutputs[i].kidOutput.signDesc = signDescriptors[i]
}
}
func TestDeserializeKidsList(t *testing.T) {
var b bytes.Buffer
for _, kid := range kidOutputs {
if err := kid.Encode(&b); err != nil {
t.Fatalf("unable to serialize and add kid output to "+ t.Fatalf("unable to serialize and add kid output to "+
"list: %v", err) "list: %v", err)
} }
@ -219,7 +303,7 @@ func TestAddSerializedKidsToList(t *testing.T) {
t.Fatalf("unable to deserialize kid output list: %v", err) t.Fatalf("unable to deserialize kid output list: %v", err)
} }
for i := 0; i < 3; i++ { for i := range kidOutputs {
if !reflect.DeepEqual(&kidOutputs[i], kidList[i]) { if !reflect.DeepEqual(&kidOutputs[i], kidList[i]) {
t.Fatalf("kidOutputs don't match \n%+v\n%+v", t.Fatalf("kidOutputs don't match \n%+v\n%+v",
&kidOutputs[i], kidList[i]) &kidOutputs[i], kidList[i])
@ -227,29 +311,47 @@ func TestAddSerializedKidsToList(t *testing.T) {
} }
} }
func TestSerializeKidOutput(t *testing.T) { func TestKidOutputSerialization(t *testing.T) {
kid := &kidOutputs[0] for i, kid := range kidOutputs {
descriptor := &signDescriptors[0] var b bytes.Buffer
pk, err := btcec.ParsePubKey(keys[0], btcec.S256()) if err := kid.Encode(&b); err != nil {
if err != nil { t.Fatalf("Encode #%d: unable to serialize "+
t.Fatalf("unable to parse pub key: %v", keys[0]) "kid output: %v", i, err)
} }
descriptor.PubKey = pk
kid.signDescriptor = descriptor
var b bytes.Buffer var deserializedKid kidOutput
if err := deserializedKid.Decode(&b); err != nil {
t.Fatalf("Decode #%d: unable to deserialize "+
"kid output: %v", i, err)
}
if !reflect.DeepEqual(kid, deserializedKid) {
t.Fatalf("DeepEqual #%d: unexpected kidOutput, "+
"want %+v, got %+v",
i, kid, deserializedKid)
}
}
}
func TestBabyOutputSerialization(t *testing.T) {
for i, baby := range babyOutputs {
var b bytes.Buffer
if err := baby.Encode(&b); err != nil {
t.Fatalf("Encode #%d: unable to serialize "+
"baby output: %v", i, err)
}
var deserializedBaby babyOutput
if err := deserializedBaby.Decode(&b); err != nil {
t.Fatalf("Decode #%d: unable to deserialize "+
"baby output: %v", i, err)
}
if !reflect.DeepEqual(baby, deserializedBaby) {
t.Fatalf("DeepEqual #%d: unexpected babyOutput, "+
"want %+v, got %+v",
i, baby, deserializedBaby)
}
if err := serializeKidOutput(&b, kid); err != nil {
t.Fatalf("unable to serialize kid output: %v", err)
}
deserializedKid, err := deserializeKidOutput(&b)
if err != nil {
t.Fatalf(err.Error())
}
if !reflect.DeepEqual(kid, deserializedKid) {
t.Fatalf("kidOutputs don't match %+v vs %+v", kid,
deserializedKid)
} }
} }