diff --git a/utxonursery.go b/utxonursery.go index 3b401489..2bad107f 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,230 @@ 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 { + // 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 { 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 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 { 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 @@ -1126,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) diff --git a/utxonursery_test.go b/utxonursery_test.go index e4062ec3..2d7b9560 100644 --- a/utxonursery_test.go +++ b/utxonursery_test.go @@ -4,6 +4,7 @@ package main import ( "bytes" + "fmt" "reflect" "testing" @@ -167,48 +168,131 @@ 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 - - for i := 0; i < 3; i++ { - kid := &kidOutputs[i] - descriptor := &signDescriptors[i] +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 { - 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 - 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 +303,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 +311,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) } }