From 47f40014f5200a17e1775e2b384fb34d43f1f421 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 28 Sep 2017 18:26:42 -0700 Subject: [PATCH 1/5] utxonursery: adds babyOutput and CsvSpendableOutput --- utxonursery.go | 322 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 210 insertions(+), 112 deletions(-) diff --git a/utxonursery.go b/utxonursery.go index 3b401489..bd74352b 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -176,13 +176,13 @@ func (u *utxoNursery) reloadPreschool(heightHint uint32) 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 { return err } - outpoint := psclOutput.outPoint - sourceTxid := outpoint.Hash + sourceTxid := psclOutput.OutPoint().Hash confChan, err := u.notifier.RegisterConfirmationsNtfn( &sourceTxid, 1, heightHint, @@ -192,7 +192,7 @@ func (u *utxoNursery) reloadPreschool(heightHint uint32) error { } utxnLog.Infof("Preschool outpoint %v re-registered for confirmation "+ - "notification.", psclOutput.outPoint) + "notification.", psclOutput.OutPoint()) go psclOutput.waitForPromotion(u.db, confChan) return nil }) @@ -250,28 +250,6 @@ func (u *utxoNursery) Stop() error { 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 // outputs until their mature, finally sweeping them into the wallet once // 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 // output to incubate. if closeSummary.SelfOutputSignDesc != nil { - outputAmt := btcutil.Amount(closeSummary.SelfOutputSignDesc.Output.Value) - selfOutput := &kidOutput{ - originChanPoint: closeSummary.ChanPoint, - amt: outputAmt, - outPoint: closeSummary.SelfOutpoint, - blocksToMaturity: closeSummary.SelfOutputMaturity, - signDescriptor: closeSummary.SelfOutputSignDesc, - witnessType: lnwallet.CommitmentTimeLock, - } + selfOutput := makeKidOutput( + &closeSummary.SelfOutpoint, + &closeSummary.ChanPoint, + closeSummary.SelfOutputMaturity, + lnwallet.CommitmentTimeLock, + closeSummary.SelfOutputSignDesc, + ) - 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 @@ -336,11 +312,11 @@ out: // We'll skip any zero value'd outputs as this // indicates we don't have a settled balance // within the commitment transaction. - if output.amt == 0 { + if output.Amount() == 0 { continue } - sourceTxid := output.outPoint.Hash + sourceTxid := output.OutPoint().Hash if err := output.enterPreschool(u.db); err != nil { 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 // information for this immature output. - immatureOutput, err := deserializeKidOutput(outputReader) - if err != nil { + var immatureOutput kidOutput + if err := immatureOutput.Decode(outputReader); err != nil { return err } // TODO(roasbeef): should actually be list of outputs report = &contractMaturityReport{ chanPoint: *chanPoint, - limboBalance: immatureOutput.amt, - maturityRequirement: immatureOutput.blocksToMaturity, + limboBalance: immatureOutput.Amount(), + maturityRequirement: immatureOutput.BlocksToMaturity(), } // If the confirmation height is set, then this means the // contract has been confirmed, and we know the final maturity // height. - if immatureOutput.confHeight != 0 { - report.confirmationHeight = immatureOutput.confHeight - report.maturityHeight = (immatureOutput.blocksToMaturity + - immatureOutput.confHeight) + if immatureOutput.ConfHeight() != 0 { + report.confirmationHeight = immatureOutput.ConfHeight() + report.maturityHeight = (immatureOutput.BlocksToMaturity() + + immatureOutput.ConfHeight()) } 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 // immature outpoint into the preschool bucket. var outpointBytes bytes.Buffer - if err := writeOutpoint(&outpointBytes, &k.outPoint); err != nil { + if err := writeOutpoint(&outpointBytes, k.OutPoint()); err != nil { return err } var kidBytes bytes.Buffer - if err := serializeKidOutput(&kidBytes, k); err != nil { + if err := k.Encode(&kidBytes); err != nil { return err } 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 // chanPoint. var b bytes.Buffer - err = writeOutpoint(&b, &k.originChanPoint) + err = writeOutpoint(&b, k.OriginChanPoint()) if err != nil { return err } @@ -573,7 +549,7 @@ func (k *kidOutput) enterPreschool(db *channeldb.DB) error { } utxnLog.Infof("Outpoint %v now in preschool, waiting for "+ - "initial confirmation", k.outPoint) + "initial confirmation", k.OutPoint()) return nil }) @@ -589,14 +565,14 @@ func (k *kidOutput) waitForPromotion(db *channeldb.DB, confChan *chainntnfs.Conf txConfirmation, ok := <-confChan.Confirmed if !ok { utxnLog.Errorf("notification chan "+ - "closed, can't advance output %v", k.outPoint) + "closed, can't advance output %v", k.OutPoint()) return } 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 // 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. err := db.Update(func(tx *bolt.Tx) error { var originPoint bytes.Buffer - if err := writeOutpoint(&originPoint, &k.originChanPoint); err != nil { + if err := writeOutpoint(&originPoint, k.OriginChanPoint()); err != nil { 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 // from the preschool bucket, as well as the secondary index. var outpointBytes bytes.Buffer - if err := writeOutpoint(&outpointBytes, &k.outPoint); err != nil { + if err := writeOutpoint(&outpointBytes, k.OutPoint()); err != nil { return err } if err := psclBucket.Delete(outpointBytes.Bytes()); err != nil { utxnLog.Errorf("unable to delete kindergarten output from "+ - "preschool bucket: %v", k.outPoint) + "preschool bucket: %v", k.OutPoint()) return err } if err := psclIndex.Delete(originPoint.Bytes()); err != nil { utxnLog.Errorf("unable to delete kindergarten output from "+ - "preschool index: %v", k.outPoint) + "preschool index: %v", k.OutPoint()) return err } @@ -642,7 +618,7 @@ func (k *kidOutput) waitForPromotion(db *channeldb.DB, confChan *chainntnfs.Conf return err } - maturityHeight := k.confHeight + k.blocksToMaturity + maturityHeight := k.ConfHeight() + k.BlocksToMaturity() heightBytes := make([]byte, 4) byteOrder.PutUint32(heightBytes, maturityHeight) @@ -660,7 +636,7 @@ func (k *kidOutput) waitForPromotion(db *channeldb.DB, confChan *chainntnfs.Conf outputOffset := len(existingOutputs) b := bytes.NewBuffer(existingOutputs) - if err := serializeKidOutput(b, k); err != nil { + if err := k.Encode(b); err != nil { return err } 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 "+ - "at height %v (delay of %v)", k.outPoint, - maturityHeight, k.blocksToMaturity) + "at height %v (delay of %v)", k.OutPoint(), + maturityHeight, k.BlocksToMaturity()) return 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 // closed within the database. for _, closedChan := range kgtnOutputs { - err := u.db.MarkChanFullyClosed(&closedChan.originChanPoint) + err := u.db.MarkChanFullyClosed(closedChan.OriginChanPoint()) if err != nil { return err } @@ -786,7 +762,7 @@ func fetchGraduatingOutputs(db *channeldb.DB, wallet *lnwallet.LightningWallet, // output or not. for _, kgtnOutput := range kgtnOutputs { 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", @@ -842,7 +818,7 @@ func createSweepTx(wallet *lnwallet.LightningWallet, var totalSum btcutil.Amount for _, o := range matureOutputs { - totalSum += o.amt + totalSum += o.Amount() } sweepTx := wire.NewMsgTx(2) @@ -852,9 +828,9 @@ func createSweepTx(wallet *lnwallet.LightningWallet, }) for _, utxo := range matureOutputs { sweepTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: utxo.outPoint, + PreviousOutPoint: *utxo.OutPoint(), // 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 { var chanPoint bytes.Buffer - err := writeOutpoint(&chanPoint, &sweptOutput.originChanPoint) + err := writeOutpoint(&chanPoint, sweptOutput.OriginChanPoint()) if err != nil { return err } @@ -964,101 +940,223 @@ func deserializeKidList(r io.Reader) ([]*kidOutput, error) { var kidOutputs []*kidOutput for { - kidOutput, err := deserializeKidOutput(r) - if err != nil { + var kid = &kidOutput{} + if err := kid.Decode(r); err != nil { if err == io.EOF { break } else { return nil, err } } - kidOutputs = append(kidOutputs, kidOutput) + kidOutputs = append(kidOutputs, kid) } return kidOutputs, nil } -// serializeKidOutput 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 serializeKidOutput(w io.Writer, kid *kidOutput) error { - var scratch [8]byte - byteOrder.PutUint64(scratch[:], uint64(kid.amt)) +// CsvSpendableOutput is a SpendableOutput that contains all of the information +// necessary to construct, sign, and sweep an output locked with a CSV delay. +type CsvSpendableOutput interface { + SpendableOutput + + // ConfHeight returns the height at which this output was confirmed. + // 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 { return err } - if err := writeOutpoint(w, &kid.outPoint); err != nil { - return err - } - if err := writeOutpoint(w, &kid.originChanPoint); err != nil { + if err := bo.timeoutTx.Serialize(w); err != nil { 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 { return err } - byteOrder.PutUint32(scratch[:4], kid.confHeight) + byteOrder.PutUint32(scratch[:4], k.ConfHeight()) if _, err := w.Write(scratch[:4]); err != nil { return err } - byteOrder.PutUint16(scratch[:2], uint16(kid.witnessType)) + byteOrder.PutUint16(scratch[:2], uint16(k.WitnessType())) if _, err := w.Write(scratch[:2]); err != nil { return err } - return lnwallet.WriteSignDescriptor(w, kid.signDescriptor) + return lnwallet.WriteSignDescriptor(w, k.SignDesc()) } -// deserializeKidOutput takes a byte array representation of a kidOutput -// and converts it to an struct. Note that the witnessFunc method isn't added -// during deserialization and must be added later based on the value of the -// witnessType field. -func deserializeKidOutput(r io.Reader) (*kidOutput, error) { - scratch := make([]byte, 8) - - kid := &kidOutput{} +// Decode takes a byte array representation of a kidOutput and converts it to an +// struct. Note that the witnessFunc method isn't added during deserialization +// and must be added later based on the value of the witnessType field. +func (k *kidOutput) Decode(r io.Reader) error { + var scratch [8]byte 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 != nil { - return nil, err + if err := readOutpoint(io.LimitReader(r, 40), &k.outpoint); err != nil { + return err } - err = readOutpoint(io.LimitReader(r, 40), &kid.originChanPoint) + err := readOutpoint(io.LimitReader(r, 40), &k.originChanPoint) if err != nil { - return nil, err + return err } 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 { - 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 { - 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{} - if err := lnwallet.ReadSignDescriptor(r, kid.signDescriptor); err != nil { - return nil, err - } - - return kid, nil + return lnwallet.ReadSignDescriptor(r, &k.signDesc) } // TODO(bvu): copied from channeldb, remove repetition From 921f0b1f43e07fd28cc053f5abe33912fc418a45 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 28 Sep 2017 18:32:44 -0700 Subject: [PATCH 2/5] utxonursery_test: adds serialization tests for baby outputs --- utxonursery_test.go | 182 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 141 insertions(+), 41 deletions(-) diff --git a/utxonursery_test.go b/utxonursery_test.go index e4062ec3..b7cc87d7 100644 --- a/utxonursery_test.go +++ b/utxonursery_test.go @@ -167,48 +167,130 @@ var ( kidOutputs = []kidOutput{ { + breachedOutput: breachedOutput{ + amt: btcutil.Amount(13e7), + outpoint: outPoints[0], + witnessType: lnwallet.CommitmentTimeLock, + }, originChanPoint: outPoints[1], - amt: btcutil.Amount(13e7), - outPoint: outPoints[0], blocksToMaturity: uint32(100), - witnessType: lnwallet.CommitmentTimeLock, confHeight: uint32(1770001), }, { + breachedOutput: breachedOutput{ + amt: btcutil.Amount(24e7), + outpoint: outPoints[1], + witnessType: lnwallet.CommitmentTimeLock, + }, originChanPoint: outPoints[0], - amt: btcutil.Amount(24e7), - outPoint: outPoints[1], blocksToMaturity: uint32(50), - witnessType: lnwallet.CommitmentTimeLock, confHeight: uint32(22342321), }, { + breachedOutput: breachedOutput{ + amt: btcutil.Amount(2e5), + outpoint: outPoints[2], + witnessType: lnwallet.CommitmentTimeLock, + }, originChanPoint: outPoints[2], - amt: btcutil.Amount(2e5), - outPoint: outPoints[2], blocksToMaturity: uint32(12), - witnessType: lnwallet.CommitmentTimeLock, 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) { - var b bytes.Buffer +func init() { + for i := range signDescriptors { - for i := 0; i < 3; i++ { - kid := &kidOutputs[i] - descriptor := &signDescriptors[i] pk, err := btcec.ParsePubKey(keys[i], btcec.S256()) if err != nil { - t.Fatalf("unable to parse pub key: %v", keys[i]) + panic("unable to parse pub key: %v") } - descriptor.PubKey = pk - kid.signDescriptor = descriptor + signDescriptors[i].PubKey = pk - 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 "+ "list: %v", err) } @@ -219,7 +301,7 @@ func TestAddSerializedKidsToList(t *testing.T) { 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]) { t.Fatalf("kidOutputs don't match \n%+v\n%+v", &kidOutputs[i], kidList[i]) @@ -227,29 +309,47 @@ func TestAddSerializedKidsToList(t *testing.T) { } } -func TestSerializeKidOutput(t *testing.T) { - kid := &kidOutputs[0] - descriptor := &signDescriptors[0] - pk, err := btcec.ParsePubKey(keys[0], btcec.S256()) - if err != nil { - t.Fatalf("unable to parse pub key: %v", keys[0]) - } - descriptor.PubKey = pk - kid.signDescriptor = descriptor +func TestKidOutputSerialization(t *testing.T) { + for i, kid := range kidOutputs { + var b bytes.Buffer + if err := kid.Encode(&b); err != nil { + t.Fatalf("Encode #%d: unable to serialize "+ + "kid output: %v", i, err) + } - 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) } } From d2e5494ef36811d20871549f51f64caa96b5d8a9 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 3 Oct 2017 16:39:38 -0700 Subject: [PATCH 3/5] utxonursery: improves documentation grammar and thoroughness --- utxonursery.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/utxonursery.go b/utxonursery.go index bd74352b..7f6925ab 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -984,13 +984,20 @@ type CsvSpendableOutput interface { // the now-mature kidOutput will be unwrapped and continue its journey through // the nursery. type babyOutput struct { - kidOutput + // expiry is the absolute block height at which the timeoutTx should be + // broadcast to the network. + expiry uint32 - 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 baby output the wraps a future kidOutput. The +// 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, @@ -1023,7 +1030,7 @@ func (bo *babyOutput) Encode(w io.Writer) error { return bo.kidOutput.Encode(w) } -// Decode reconstructs a baby output using the provide io.Reader. +// 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 { From 0dcb620194e9f788f5adedba6e8e13ce8830e2d8 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 3 Oct 2017 16:41:19 -0700 Subject: [PATCH 4/5] utxonursery_test: properly print error msg during init failure --- utxonursery_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/utxonursery_test.go b/utxonursery_test.go index b7cc87d7..2d7b9560 100644 --- a/utxonursery_test.go +++ b/utxonursery_test.go @@ -4,6 +4,7 @@ package main import ( "bytes" + "fmt" "reflect" "testing" @@ -273,11 +274,12 @@ var ( ) func init() { + // 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 := range signDescriptors { - pk, err := btcec.ParsePubKey(keys[i], btcec.S256()) if err != nil { - panic("unable to parse pub key: %v") + panic(fmt.Sprintf("unable to parse pub key during init: %v", err)) } signDescriptors[i].PubKey = pk From d8688b7d674b50916cb5ed7925af5b1ddbb2b609 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 5 Oct 2017 18:13:48 -0700 Subject: [PATCH 5/5] utxonursery: adds compile-time iface check for baby and kid outputs --- utxonursery.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/utxonursery.go b/utxonursery.go index 7f6925ab..2bad107f 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -1231,3 +1231,8 @@ func readTxOut(r io.Reader, txo *wire.TxOut) error { return nil } + +// Compile-time constraint to ensure kidOutput and babyOutpt implement the +// CsvSpendableOutput interface. +var _ CsvSpendableOutput = (*kidOutput)(nil) +var _ CsvSpendableOutput = (*babyOutput)(nil)