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 {
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)

@ -4,6 +4,7 @@ package main
import (
"bytes"
"fmt"
"reflect"
"testing"
@ -167,48 +168,131 @@ var (
kidOutputs = []kidOutput{
{
originChanPoint: outPoints[1],
breachedOutput: breachedOutput{
amt: btcutil.Amount(13e7),
outPoint: outPoints[0],
blocksToMaturity: uint32(100),
outpoint: outPoints[0],
witnessType: lnwallet.CommitmentTimeLock,
},
originChanPoint: outPoints[1],
blocksToMaturity: uint32(100),
confHeight: uint32(1770001),
},
{
originChanPoint: outPoints[0],
breachedOutput: breachedOutput{
amt: btcutil.Amount(24e7),
outPoint: outPoints[1],
blocksToMaturity: uint32(50),
outpoint: outPoints[1],
witnessType: lnwallet.CommitmentTimeLock,
},
originChanPoint: outPoints[0],
blocksToMaturity: uint32(50),
confHeight: uint32(22342321),
},
{
originChanPoint: outPoints[2],
breachedOutput: breachedOutput{
amt: btcutil.Amount(2e5),
outPoint: outPoints[2],
blocksToMaturity: uint32(12),
outpoint: outPoints[2],
witnessType: lnwallet.CommitmentTimeLock,
},
originChanPoint: outPoints[2],
blocksToMaturity: uint32(12),
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 := serializeKidOutput(&b, kid); err != nil {
t.Fatalf("unable to serialize kid output: %v", err)
if err := kid.Encode(&b); err != nil {
t.Fatalf("Encode #%d: unable to serialize "+
"kid output: %v", i, err)
}
deserializedKid, err := deserializeKidOutput(&b)
if err != nil {
t.Fatalf(err.Error())
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("kidOutputs don't match %+v vs %+v", 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)
}
}
}