From 701c4a974f90cfc8a4c48966e5afdecd8e4bcd0d Mon Sep 17 00:00:00 2001 From: Philip Hayes Date: Sun, 7 May 2017 03:34:00 -0700 Subject: [PATCH 01/15] channeldb+utxonursery+lnwire: use lnwire's OutPoint,TxOut serialization --- chainntnfs/btcdnotify/btcd.go | 2 +- channeldb/channel.go | 76 ++++++++++------------------------- channeldb/db.go | 6 +-- channeldb/graph.go | 14 +++---- lnwire/lnwire.go | 41 +------------------ lnwire/outpoint.go | 59 +++++++++++++++++++++++++++ lnwire/outpoint_test.go | 40 ++++++++++++++++++ lnwire/txout.go | 46 +++++++++++++++++++++ lnwire/txout_test.go | 46 +++++++++++++++++++++ utxonursery.go | 76 +++-------------------------------- 10 files changed, 232 insertions(+), 174 deletions(-) create mode 100644 lnwire/outpoint.go create mode 100644 lnwire/outpoint_test.go create mode 100644 lnwire/txout.go create mode 100644 lnwire/txout_test.go diff --git a/chainntnfs/btcdnotify/btcd.go b/chainntnfs/btcdnotify/btcd.go index 8683ef05ad73..b4dab0e05433 100644 --- a/chainntnfs/btcdnotify/btcd.go +++ b/chainntnfs/btcdnotify/btcd.go @@ -567,7 +567,7 @@ func (b *BtcdNotifier) checkConfirmationTrigger(txSha *chainhash.Hash, if confClients, ok := b.confNotifications[*txSha]; ok { // Either all of the registered confirmations will be // dispatched due to a single confirmation, or added to the - // conf head. Therefor we unconditionally delete the registered + // conf head. Therefore we unconditionally delete the registered // confirmations from the staging zone. defer func() { delete(b.confNotifications, *txSha) diff --git a/channeldb/channel.go b/channeldb/channel.go index 6640db305a1b..b44375ca303a 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -380,7 +380,7 @@ func (c *OpenChannel) fullSync(tx *bolt.Tx) error { return err } var b bytes.Buffer - if err := writeOutpoint(&b, &c.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, c.ChanID); err != nil { return err } if chanIndexBucket.Get(b.Bytes()) == nil { @@ -871,7 +871,7 @@ func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary) error { } var b bytes.Buffer - if err := writeOutpoint(&b, &c.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, c.ChanID); err != nil { return err } @@ -982,7 +982,7 @@ func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error { return err } - if err := writeOutpoint(w, &cs.ChanPoint); err != nil { + if err := lnwire.WriteOutPoint(w, &cs.ChanPoint); err != nil { return err } if _, err := w.Write(cs.ClosingTXID[:]); err != nil { @@ -1037,7 +1037,7 @@ func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) { return nil, err } - if err := readOutpoint(r, &c.ChanPoint); err != nil { + if err := lnwire.ReadOutPoint(r, &c.ChanPoint); err != nil { return nil, err } if _, err := io.ReadFull(r, c.ClosingTXID[:]); err != nil { @@ -1245,7 +1245,7 @@ func putChanCapacity(openChanBucket *bolt.Bucket, channel *OpenChannel) error { scratch3 := make([]byte, 8) var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1290,7 +1290,7 @@ func deleteChanCapacity(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanCapacity(openChanBucket *bolt.Bucket, channel *OpenChannel) error { // A byte slice re-used to compute each key prefix below. var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1317,7 +1317,7 @@ func putChanFeePerKw(openChanBucket *bolt.Bucket, channel *OpenChannel) error { byteOrder.PutUint64(scratch, uint64(channel.FeePerKw)) var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1337,7 +1337,7 @@ func deleteChanMinFeePerKw(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanMinFeePerKw(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1356,7 +1356,7 @@ func putChanNumUpdates(openChanBucket *bolt.Bucket, channel *OpenChannel) error byteOrder.PutUint64(scratch, channel.NumUpdates) var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1376,7 +1376,7 @@ func deleteChanNumUpdates(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanNumUpdates(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1395,7 +1395,7 @@ func putChanAmountsTransferred(openChanBucket *bolt.Bucket, channel *OpenChannel scratch2 := make([]byte, 8) var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1428,7 +1428,7 @@ func deleteChanAmountsTransferred(openChanBucket *bolt.Bucket, chanID []byte) er func fetchChanAmountsTransferred(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1450,7 +1450,7 @@ func putChanIsPending(openChanBucket *bolt.Bucket, channel *OpenChannel) error { scratch := make([]byte, 2) var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1476,7 +1476,7 @@ func deleteChanIsPending(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanIsPending(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1541,7 +1541,7 @@ func deleteChanConfInfo(openChanBucket *bolt.Bucket, chanID []byte) error { func putChannelIDs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { // TODO(roasbeef): just pass in chanID everywhere for puts var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1568,7 +1568,7 @@ func fetchChannelIDs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { b bytes.Buffer ) - if err = writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err = lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } @@ -1604,7 +1604,7 @@ func putChanCommitFee(openChanBucket *bolt.Bucket, channel *OpenChannel) error { func fetchChanCommitFee(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1628,7 +1628,7 @@ func deleteChanCommitFee(openChanBucket *bolt.Bucket, chanID []byte) error { func putChanCommitTxns(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var bc bytes.Buffer - if err := writeOutpoint(&bc, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutpoint(&bc, &channel.FundingOutpoint); err != nil { return err } txnsKey := make([]byte, len(commitTxnsKey)+bc.Len()) @@ -1658,7 +1658,7 @@ func deleteChanCommitTxns(nodeChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanCommitTxns(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var bc bytes.Buffer var err error - if err = writeOutpoint(&bc, &channel.FundingOutpoint); err != nil { + if err = lnwire.WriteOutPoint(&bc, channel.ChanID); err != nil { return err } txnsKey := make([]byte, len(commitTxnsKey)+bc.Len()) @@ -1745,7 +1745,7 @@ func putChanConfigs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { func fetchChanConfigs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var bc bytes.Buffer - if err := writeOutpoint(&bc, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&bc, channel.ChanID); err != nil { return err } configKey := make([]byte, len(chanConfigPrefix)+len(bc.Bytes())) @@ -1885,7 +1885,7 @@ func deleteChanFundingInfo(nodeChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } fundTxnKey := make([]byte, len(fundingTxnKey)+b.Len()) @@ -1971,7 +1971,7 @@ func deleteChanRevocationState(nodeChanBucket *bolt.Bucket, chanID []byte) error func fetchChanRevocationState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { return err } preimageKey := make([]byte, len(revocationStateKey)+b.Len()) @@ -2316,38 +2316,6 @@ func wipeChannelLogEntries(log *bolt.Bucket, o *wire.OutPoint) error { return nil } -func writeOutpoint(w io.Writer, o *wire.OutPoint) error { - // TODO(roasbeef): make all scratch buffers on the stack - scratch := make([]byte, 4) - - // TODO(roasbeef): write raw 32 bytes instead of wasting the extra - // byte. - if err := wire.WriteVarBytes(w, 0, o.Hash[:]); err != nil { - return err - } - - byteOrder.PutUint32(scratch, o.Index) - _, err := w.Write(scratch) - return err -} - -func readOutpoint(r io.Reader, o *wire.OutPoint) error { - scratch := make([]byte, 4) - - txid, err := wire.ReadVarBytes(r, 0, 32, "prevout") - if err != nil { - return err - } - copy(o.Hash[:], txid) - - if _, err := r.Read(scratch); err != nil { - return err - } - o.Index = byteOrder.Uint32(scratch) - - return nil -} - func writeBool(w io.Writer, b bool) error { boolByte := byte(0x01) if !b { diff --git a/channeldb/db.go b/channeldb/db.go index 59c77f04fa4d..8a3c84d3ff20 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -269,7 +269,7 @@ func (d *DB) fetchNodeChannels(openChanBucket, outBytes := bytes.NewReader(k) chanID := &wire.OutPoint{} - if err := readOutpoint(outBytes, chanID); err != nil { + if err := lnwire.ReadOutPoint(outBytes, chanID); err != nil { return err } @@ -373,7 +373,7 @@ func (d *DB) MarkChannelAsOpen(outpoint *wire.OutPoint, // Generate the database key, which will consist of the // IsPending prefix followed by the channel's outpoint. var b bytes.Buffer - if err := writeOutpoint(&b, outpoint); err != nil { + if err := lnwire.WriteOutPoint(&b, outpoint); err != nil { return err } keyPrefix := make([]byte, 3+b.Len()) @@ -455,7 +455,7 @@ func (d *DB) FetchClosedChannels(pendingOnly bool) ([]*ChannelCloseSummary, erro func (d *DB) MarkChanFullyClosed(chanPoint *wire.OutPoint) error { return d.Update(func(tx *bolt.Tx) error { var b bytes.Buffer - if err := writeOutpoint(&b, chanPoint); err != nil { + if err := lnwire.WriteOutPoint(&b, chanPoint); err != nil { return err } diff --git a/channeldb/graph.go b/channeldb/graph.go index 711f817f5e7e..a8e95364cbdd 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -456,7 +456,7 @@ func (c *ChannelGraph) AddChannelEdge(edge *ChannelEdgeInfo) error { // Finally we add it to the channel index which maps channel // points (outpoints) to the shorter channel ID's. var b bytes.Buffer - if err := writeOutpoint(&b, &edge.ChannelPoint); err != nil { + if err := lnwire.WriteOutPoint(&b, &edge.ChannelPoint); err != nil { return err } return chanIndex.Put(b.Bytes(), chanKey[:]) @@ -600,7 +600,7 @@ func (c *ChannelGraph) PruneGraph(spentOutputs []*wire.OutPoint, // if NOT if filter var opBytes bytes.Buffer - if err := writeOutpoint(&opBytes, chanPoint); err != nil { + if err := lnwire.WriteOutPoint(&opBytes, chanPoint); err != nil { return nil } @@ -724,7 +724,7 @@ func (c *ChannelGraph) ChannelID(chanPoint *wire.OutPoint) (uint64, error) { var chanID uint64 var b bytes.Buffer - if err := writeOutpoint(&b, chanPoint); err != nil { + if err := lnwire.WriteOutPoint(&b, chanPoint); err != nil { return 0, nil } @@ -756,7 +756,7 @@ func (c *ChannelGraph) ChannelID(chanPoint *wire.OutPoint) (uint64, error) { func delChannelByEdge(edges *bolt.Bucket, edgeIndex *bolt.Bucket, chanIndex *bolt.Bucket, chanPoint *wire.OutPoint) error { var b bytes.Buffer - if err := writeOutpoint(&b, chanPoint); err != nil { + if err := lnwire.WriteOutPoint(&b, chanPoint); err != nil { return err } @@ -1271,7 +1271,7 @@ func (c *ChannelGraph) FetchChannelEdgesByOutpoint(op *wire.OutPoint) (*ChannelE return err } var b bytes.Buffer - if err := writeOutpoint(&b, op); err != nil { + if err := lnwire.WriteOutPoint(&b, op); err != nil { return err } chanID := chanIndex.Get(b.Bytes()) @@ -1692,7 +1692,7 @@ func putChanEdgeInfo(edgeIndex *bolt.Bucket, edgeInfo *ChannelEdgeInfo, chanID [ return err } - if err := writeOutpoint(&b, &edgeInfo.ChannelPoint); err != nil { + if err := lnwire.WriteOutPoint(&b, &edgeInfo.ChannelPoint); err != nil { return err } if err := binary.Write(&b, byteOrder, uint64(edgeInfo.Capacity)); err != nil { @@ -1794,7 +1794,7 @@ func deserializeChanEdgeInfo(r io.Reader) (*ChannelEdgeInfo, error) { } edgeInfo.ChannelPoint = wire.OutPoint{} - if err := readOutpoint(r, &edgeInfo.ChannelPoint); err != nil { + if err := lnwire.ReadOutPoint(r, &edgeInfo.ChannelPoint); err != nil { return nil, err } if err := binary.Read(r, byteOrder, &edgeInfo.Capacity); err != nil { diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 8cf8b07c088e..98bc10423dd3 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -4,13 +4,11 @@ import ( "encoding/binary" "fmt" "io" - "math" "net" "github.com/go-errors/errors" "github.com/roasbeef/btcd/btcec" - "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) @@ -189,21 +187,7 @@ func writeElement(w io.Writer, element interface{}) error { } case wire.OutPoint: - var h [32]byte - copy(h[:], e.Hash[:]) - if _, err := w.Write(h[:]); err != nil { - return err - } - - if e.Index > math.MaxUint16 { - return fmt.Errorf("index for outpoint (%v) is "+ - "greater than max index of %v", e.Index, - math.MaxUint16) - } - - var idx [2]byte - binary.BigEndian.PutUint16(idx[:], uint16(e.Index)) - if _, err := w.Write(idx[:]); err != nil { + if err := WriteOutPoint(w, &e); err != nil { return err } @@ -479,28 +463,7 @@ func readElement(r io.Reader, element interface{}) error { } *e = pkScript case *wire.OutPoint: - var h [32]byte - if _, err = io.ReadFull(r, h[:]); err != nil { - return err - } - hash, err := chainhash.NewHash(h[:]) - if err != nil { - return err - } - - var idxBytes [2]byte - _, err = io.ReadFull(r, idxBytes[:]) - if err != nil { - return err - } - index := binary.BigEndian.Uint16(idxBytes[:]) - - *e = wire.OutPoint{ - Hash: *hash, - Index: uint32(index), - } - case *FailCode: - if err := readElement(r, (*uint16)(e)); err != nil { + if err := ReadOutPoint(r, e); err != nil { return err } diff --git a/lnwire/outpoint.go b/lnwire/outpoint.go new file mode 100644 index 000000000000..b990f7f017ed --- /dev/null +++ b/lnwire/outpoint.go @@ -0,0 +1,59 @@ +package lnwire + +import ( + "encoding/binary" + "fmt" + "io" + "math" + + "github.com/roasbeef/btcd/chaincfg/chainhash" + "github.com/roasbeef/btcd/wire" +) + +// WriteOutPoint serializes a wire.OutPoint struct into the passed io.Writer +// stream. +func WriteOutPoint(w io.Writer, o *wire.OutPoint) error { + if _, err := w.Write(o.Hash[:chainhash.HashSize]); err != nil { + return err + } + + if o.Index > math.MaxUint16 { + return fmt.Errorf("index for outpoint (%v) is "+ + "greater than max index of %v", o.Index, math.MaxUint16) + } + + var idx [2]byte + binary.BigEndian.PutUint16(idx[:], uint16(o.Index)) + if _, err := w.Write(idx[:]); err != nil { + return err + } + + return nil +} + +// ReadOutPoint deserializes a wire.OutPoint struct from the passed io.Reader +// stream. +func ReadOutPoint(r io.Reader, o *wire.OutPoint) error { + var h [chainhash.HashSize]byte + if _, err := io.ReadFull(r, h[:]); err != nil { + return err + } + hash, err := chainhash.NewHash(h[:]) + if err != nil { + return err + } + + var idxBytes [2]byte + _, err = io.ReadFull(r, idxBytes[:]) + if err != nil { + return err + } + index := binary.BigEndian.Uint16(idxBytes[:]) + + *o = wire.OutPoint{ + Hash: *hash, + Index: uint32(index), + } + + return nil +} diff --git a/lnwire/outpoint_test.go b/lnwire/outpoint_test.go new file mode 100644 index 000000000000..9e33e9d21ded --- /dev/null +++ b/lnwire/outpoint_test.go @@ -0,0 +1,40 @@ +package lnwire + +import ( + "bytes" + "reflect" + "testing" + + "github.com/roasbeef/btcd/chaincfg/chainhash" + "github.com/roasbeef/btcd/wire" +) + +func TestOutpointSerialization(t *testing.T) { + outpoint := wire.OutPoint{ + Hash: [chainhash.HashSize]byte{ + 0x51, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, + 0x48, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, + 0x2d, 0xe7, 0x93, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, + 0x1f, 0xb, 0x4c, 0xf9, 0x9e, 0xc5, 0x8c, 0xe9, + }, + Index: 9, + } + + var buf bytes.Buffer + + if err := WriteOutPoint(&buf, &outpoint); err != nil { + t.Fatalf("unable to serialize outpoint: %v", err) + } + + var deserializedOutpoint wire.OutPoint + if err := ReadOutPoint(&buf, &deserializedOutpoint); err != nil { + t.Fatalf("unable to deserialize outpoint: %v", err) + } + + if !reflect.DeepEqual(outpoint, deserializedOutpoint) { + t.Fatalf("original and deserialized outpoints are different:\n"+ + "original : %+v\n"+ + "deserialized : %+v\n", + outpoint, deserializedOutpoint) + } +} diff --git a/lnwire/txout.go b/lnwire/txout.go new file mode 100644 index 000000000000..f75f51a84b33 --- /dev/null +++ b/lnwire/txout.go @@ -0,0 +1,46 @@ +package lnwire + +import ( + "encoding/binary" + "io" + + "github.com/roasbeef/btcd/wire" +) + +// WriteTxOut serializes a wire.TxOut struct into the passed io.Writer stream. +func WriteTxOut(w io.Writer, txo *wire.TxOut) error { + var scratch [8]byte + + binary.BigEndian.PutUint64(scratch[:], uint64(txo.Value)) + if _, err := w.Write(scratch[:]); err != nil { + return err + } + + if err := wire.WriteVarBytes(w, 0, txo.PkScript); err != nil { + return err + } + + return nil +} + +// ReadTxOut deserializes a wire.TxOut struct from the passed io.Reader stream. +func ReadTxOut(r io.Reader, txo *wire.TxOut) error { + var scratch [8]byte + + if _, err := io.ReadFull(r, scratch[:]); err != nil { + return err + } + value := int64(binary.BigEndian.Uint64(scratch[:])) + + pkScript, err := wire.ReadVarBytes(r, 0, 80, "pkScript") + if err != nil { + return err + } + + *txo = wire.TxOut{ + Value: value, + PkScript: pkScript, + } + + return nil +} diff --git a/lnwire/txout_test.go b/lnwire/txout_test.go new file mode 100644 index 000000000000..4fcd9d1359cb --- /dev/null +++ b/lnwire/txout_test.go @@ -0,0 +1,46 @@ +package lnwire + +import ( + "bytes" + "reflect" + "testing" + + "github.com/roasbeef/btcd/wire" +) + +func TestTxOutSerialization(t *testing.T) { + txo := wire.TxOut{ + Value: 1e7, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + } + + var buf bytes.Buffer + + if err := WriteTxOut(&buf, &txo); err != nil { + t.Fatalf("unable to serialize txout: %v", err) + } + + var deserializedTxo wire.TxOut + if err := ReadTxOut(&buf, &deserializedTxo); err != nil { + t.Fatalf("unable to deserialize txout: %v", err) + } + + if !reflect.DeepEqual(txo, deserializedTxo) { + t.Fatalf("original and deserialized txouts are different:\n"+ + "original : %+v\n"+ + "deserialized : %+v\n", + txo, deserializedTxo) + } +} diff --git a/utxonursery.go b/utxonursery.go index 911ef651e259..17780cd6b4da 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -15,6 +15,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwallet" "github.com/roasbeef/btcd/btcec" + "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" @@ -584,7 +585,7 @@ 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 := lnwire.WriteOutPoint(&outpointBytes, &k.outPoint); err != nil { return err } var kidBytes bytes.Buffer @@ -658,7 +659,7 @@ 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 := lnwire.WriteOutPoint(&outpointBytes, &k.outPoint); err != nil { return err } if err := psclBucket.Delete(outpointBytes.Bytes()); err != nil { @@ -1027,7 +1028,7 @@ func serializeKidOutput(w io.Writer, kid *kidOutput) error { return err } - if err := writeOutpoint(w, &kid.outPoint); err != nil { + if err := lnwire.WriteOutPoint(w, &kid.outPoint); err != nil { return err } if err := writeOutpoint(w, &kid.originChanPoint); err != nil { @@ -1085,7 +1086,8 @@ func deserializeKidOutput(r io.Reader) (*kidOutput, error) { } kid.amt = btcutil.Amount(byteOrder.Uint64(scratch[:])) - if err := readOutpoint(io.LimitReader(r, 40), &kid.outPoint); err != nil { + err := lnwire.ReadOutPoint(io.LimitReader(r, 40), &kid.outPoint) + if err != nil { return nil, err } if err := readOutpoint(io.LimitReader(r, 40), &kid.originChanPoint); err != nil { @@ -1145,69 +1147,3 @@ func deserializeKidOutput(r io.Reader) (*kidOutput, error) { return kid, nil } - -// TODO(bvu): copied from channeldb, remove repetition -func writeOutpoint(w io.Writer, o *wire.OutPoint) error { - // TODO(roasbeef): make all scratch buffers on the stack - scratch := make([]byte, 4) - - // TODO(roasbeef): write raw 32 bytes instead of wasting the extra - // byte. - if err := wire.WriteVarBytes(w, 0, o.Hash[:]); err != nil { - return err - } - - byteOrder.PutUint32(scratch, o.Index) - _, err := w.Write(scratch) - return err -} - -// TODO(bvu): copied from channeldb, remove repetition -func readOutpoint(r io.Reader, o *wire.OutPoint) error { - scratch := make([]byte, 4) - - txid, err := wire.ReadVarBytes(r, 0, 32, "prevout") - if err != nil { - return err - } - copy(o.Hash[:], txid) - - if _, err := r.Read(scratch); err != nil { - return err - } - o.Index = byteOrder.Uint32(scratch) - - return nil -} - -func writeTxOut(w io.Writer, txo *wire.TxOut) error { - scratch := make([]byte, 8) - - byteOrder.PutUint64(scratch, uint64(txo.Value)) - if _, err := w.Write(scratch); err != nil { - return err - } - - if err := wire.WriteVarBytes(w, 0, txo.PkScript); err != nil { - return err - } - - return nil -} - -func readTxOut(r io.Reader, txo *wire.TxOut) error { - scratch := make([]byte, 8) - - if _, err := r.Read(scratch); err != nil { - return err - } - txo.Value = int64(byteOrder.Uint64(scratch)) - - pkScript, err := wire.ReadVarBytes(r, 0, 80, "pkScript") - if err != nil { - return err - } - txo.PkScript = pkScript - - return nil -} From 32d1145f98f3bfe5d2c3dfd340776c96fa43a547 Mon Sep 17 00:00:00 2001 From: Philip Hayes Date: Sun, 7 May 2017 04:04:28 -0700 Subject: [PATCH 02/15] lnwallet: extract SignDescriptor serialization and witness generator --- lnwallet/interface.go | 1 - lnwallet/signdescriptor.go | 134 ++++++++++++++++++++++++++++++++ lnwallet/signdescriptor_test.go | 122 +++++++++++++++++++++++++++++ lnwallet/witnessgen.go | 59 ++++++++++++++ utxonursery.go | 58 +++----------- 5 files changed, 324 insertions(+), 50 deletions(-) create mode 100644 lnwallet/signdescriptor.go create mode 100644 lnwallet/signdescriptor_test.go create mode 100644 lnwallet/witnessgen.go diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 5dbc4d50d4f3..92f6adacbb9a 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -7,7 +7,6 @@ import ( "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" - "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) diff --git a/lnwallet/signdescriptor.go b/lnwallet/signdescriptor.go new file mode 100644 index 000000000000..8e1ff3918f86 --- /dev/null +++ b/lnwallet/signdescriptor.go @@ -0,0 +1,134 @@ +package lnwallet + +import ( + "encoding/binary" + "io" + + "github.com/lightningnetwork/lnd/lnwire" + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/txscript" + "github.com/roasbeef/btcd/wire" +) + +// SignDescriptor houses the necessary information required to successfully sign +// a given output. This struct is used by the Signer interface in order to gain +// access to critical data needed to generate a valid signature. +type SignDescriptor struct { + // Pubkey is the public key to which the signature should be generated + // over. The Signer should then generate a signature with the private + // key corresponding to this public key. + PubKey *btcec.PublicKey + + // PrivateTweak is a scalar value that should be added to the private + // key corresponding to the above public key to obtain the private key + // to be used to sign this input. This value is typically a leaf node + // from the revocation tree. + // + // NOTE: If this value is nil, then the input can be signed using only + // the above public key. + PrivateTweak []byte + + // WitnessScript is the full script required to properly redeem the + // output. This field will only be populated if a p2wsh or a p2sh + // output is being signed. + WitnessScript []byte + + // Output is the target output which should be signed. The PkScript and + // Value fields within the output should be properly populated, + // otherwise an invalid signature may be generated. + Output *wire.TxOut + + // HashType is the target sighash type that should be used when + // generating the final sighash, and signature. + HashType txscript.SigHashType + + // SigHashes is the pre-computed sighash midstate to be used when + // generating the final sighash for signing. + SigHashes *txscript.TxSigHashes + + // InputIndex is the target input within the transaction that should be + // signed. + InputIndex int +} + +// WriteSignDescriptor serializes a SignDescriptor struct into the passed +// io.Writer stream. +// NOTE: We assume the SigHashes and InputIndex fields haven't been assigned +// yet, since that is usually done just before broadcast by the witness +// generator. +func WriteSignDescriptor(w io.Writer, sd *SignDescriptor) error { + serializedPubKey := sd.PubKey.SerializeCompressed() + if err := wire.WriteVarBytes(w, 0, serializedPubKey); err != nil { + return err + } + + if err := wire.WriteVarBytes(w, 0, sd.PrivateTweak); err != nil { + return err + } + + if err := wire.WriteVarBytes(w, 0, sd.WitnessScript); err != nil { + return err + } + + if err := lnwire.WriteTxOut(w, sd.Output); err != nil { + return err + } + + var scratch [4]byte + binary.BigEndian.PutUint32(scratch[:], uint32(sd.HashType)) + if _, err := w.Write(scratch[:]); err != nil { + return err + } + + return nil +} + +// ReadSignDescriptor deserializes a SignDescriptor struct from the passed +// io.Reader stream. +func ReadSignDescriptor(r io.Reader, sd *SignDescriptor) error { + + pubKeyBytes, err := wire.ReadVarBytes(r, 0, 34, "pubkey") + if err != nil { + return err + } + sd.PubKey, err = btcec.ParsePubKey(pubKeyBytes, btcec.S256()) + if err != nil { + return err + } + + privateTweak, err := wire.ReadVarBytes(r, 0, 32, "privateTweak") + if err != nil { + return err + } + + // Serializing a SignDescriptor with a nil-valued PrivateTweak results in + // deserializing a zero-length slice. Since a nil-valued PrivateTweak has + // special meaning and a zero-length slice for a PrivateTweak is invalid, + // we can use the zero-length slice as the flag for a nil-valued + // PrivateTweak. + if len(privateTweak) == 0 { + sd.PrivateTweak = nil + } else { + sd.PrivateTweak = privateTweak + } + + witnessScript, err := wire.ReadVarBytes(r, 0, 100, "witnessScript") + if err != nil { + return err + } + sd.WitnessScript = witnessScript + + txOut := &wire.TxOut{} + if err := lnwire.ReadTxOut(r, txOut); err != nil { + return err + } + sd.Output = txOut + + var hashType [4]byte + if _, err := io.ReadFull(r, hashType[:]); err != nil { + return err + } + sd.HashType = txscript.SigHashType(binary.BigEndian.Uint32(hashType[:])) + + return nil +} diff --git a/lnwallet/signdescriptor_test.go b/lnwallet/signdescriptor_test.go new file mode 100644 index 000000000000..ddd22f34dc38 --- /dev/null +++ b/lnwallet/signdescriptor_test.go @@ -0,0 +1,122 @@ +package lnwallet + +import ( + "bytes" + "reflect" + "testing" + + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/txscript" + "github.com/roasbeef/btcd/wire" +) + +func TestSignDescriptorSerialization(t *testing.T) { + keys := [][]byte{ + {0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, + 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, + 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, + 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, + 0xea, 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, + 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, + 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, + 0xb4, 0x12, 0xa3, + }, + {0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, + 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, + 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, + 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, + 0xea, 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, + 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, + 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, + 0xb4, 0x12, 0xa3, + }, + } + + signDescriptors := []SignDescriptor{ + { + PrivateTweak: []byte{ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, + }, + WitnessScript: []byte{ + 0x00, 0x14, 0xee, 0x91, 0x41, 0x7e, 0x85, 0x6c, 0xde, + 0x10, 0xa2, 0x91, 0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2, + 0xef, 0xb5, 0x71, 0x48, + }, + Output: &wire.TxOut{ + Value: 5000000000, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + HashType: txscript.SigHashAll, + }, + + // Test serializing a SignDescriptor with a nil-valued PrivateTweak + { + PrivateTweak: nil, + WitnessScript: []byte{ + 0x00, 0x14, 0xee, 0x91, 0x41, 0x7e, 0x85, 0x6c, 0xde, + 0x10, 0xa2, 0x91, 0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2, + 0xef, 0xb5, 0x71, 0x48, + }, + Output: &wire.TxOut{ + Value: 5000000000, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + HashType: txscript.SigHashAll, + }, + } + + for i := 0; i < len(signDescriptors); i++ { + // Parse pubkeys for each sign descriptor. + sd := &signDescriptors[i] + pubkey, err := btcec.ParsePubKey(keys[i], btcec.S256()) + if err != nil { + t.Fatalf("unable to parse pubkey: %v", err) + } + sd.PubKey = pubkey + + // Test that serialize -> deserialize yields same result as original. + var buf bytes.Buffer + if err := WriteSignDescriptor(&buf, sd); err != nil { + t.Fatalf("unable to serialize sign descriptor[%v]: %v", i, sd) + } + + desSd := &SignDescriptor{} + if err := ReadSignDescriptor(&buf, desSd); err != nil { + t.Fatalf("unable to deserialize sign descriptor[%v]: %v", i, sd) + } + + if !reflect.DeepEqual(sd, desSd) { + t.Fatalf("original and deserialized sign descriptors not equal:\n"+ + "original : %+v\n"+ + "deserialized : %+v\n", + sd, desSd) + } + } +} diff --git a/lnwallet/witnessgen.go b/lnwallet/witnessgen.go new file mode 100644 index 000000000000..77fd6995a802 --- /dev/null +++ b/lnwallet/witnessgen.go @@ -0,0 +1,59 @@ +package lnwallet + +import ( + "fmt" + + "github.com/roasbeef/btcd/txscript" + "github.com/roasbeef/btcd/wire" +) + +// WitnessType determines how an output's witness will be generated. The +// default commitmentTimeLock type will generate a witness that will allow +// spending of a time-locked transaction enforced by CheckSequenceVerify. +type WitnessType uint16 + +const ( + // Witness that allows us to spend the output of a commitment transaction + // after a relative lock-time lockout. + CommitmentTimeLock WitnessType = 0 + + // Witness that allows us to spend a settled no-delay output immediately on + // a counterparty's commitment transaction. + CommitmentNoDelay WitnessType = 1 + + // Witness that allows us to sweep the settled output of a malicious + // counterparty's who broadcasts a revoked commitment transaction. + CommitmentRevoke WitnessType = 2 +) + +// WitnessGenerator represents a function which is able to generate the final +// witness for a particular public key script. This function acts as an +// abstraction layer, hiding the details of the underlying script. +type WitnessGenerator func(tx *wire.MsgTx, hc *txscript.TxSigHashes, + inputIndex int) ([][]byte, error) + +// GenWitnessFunc will return a WitnessGenerator function that an output +// uses to generate the witness for a sweep transaction. +func (wt WitnessType) GenWitnessFunc(signer *Signer, + descriptor *SignDescriptor) WitnessGenerator { + + return func(tx *wire.MsgTx, hc *txscript.TxSigHashes, + inputIndex int) ([][]byte, error) { + + desc := descriptor + desc.SigHashes = hc + desc.InputIndex = inputIndex + + switch wt { + case CommitmentTimeLock: + return CommitSpendTimeout(*signer, desc, tx) + case CommitmentNoDelay: + return CommitSpendNoDelay(*signer, desc, tx) + case CommitmentRevoke: + return CommitSpendRevoke(*signer, desc, tx) + default: + return nil, fmt.Errorf("unknown witness type: %v", wt) + } + } + +} diff --git a/utxonursery.go b/utxonursery.go index 17780cd6b4da..09cf2b5de8b1 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -14,7 +14,6 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwallet" - "github.com/roasbeef/btcd/btcec" "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" @@ -77,44 +76,6 @@ var ( ErrContractNotFound = fmt.Errorf("unable to locate contract") ) -// witnessType determines how an output's witness will be generated. The -// default commitmentTimeLock type will generate a witness that will allow -// spending of a time-locked transaction enforced by CheckSequenceVerify. -type witnessType uint16 - -const ( - commitmentTimeLock witnessType = 0 -) - -// witnessGenerator represents a function which is able to generate the final -// witness for a particular public key script. This function acts as an -// abstraction layer, hiding the details of the underlying script from the -// utxoNursery. -type witnessGenerator func(tx *wire.MsgTx, hc *txscript.TxSigHashes, - inputIndex int) ([][]byte, error) - -// generateFunc will return the witnessGenerator function that a kidOutput uses -// to generate the witness for a sweep transaction. Currently there is only one -// witnessType but this will be expanded. -func (wt witnessType) generateFunc(signer *lnwallet.Signer, - descriptor *lnwallet.SignDescriptor) witnessGenerator { - - switch wt { - case commitmentTimeLock: - return func(tx *wire.MsgTx, hc *txscript.TxSigHashes, - inputIndex int) ([][]byte, error) { - - desc := descriptor - desc.SigHashes = hc - desc.InputIndex = inputIndex - - return lnwallet.CommitSpendTimeout(*signer, desc, tx) - } - } - - return nil -} - // utxoNursery is a system dedicated to incubating time-locked outputs created // by the broadcast of a commitment transaction either by us, or the remote // peer. The nursery accepts outputs and "incubates" them until they've reached @@ -292,7 +253,7 @@ func (u *utxoNursery) Stop() error { // 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 +// 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? @@ -302,13 +263,14 @@ type kidOutput struct { amt btcutil.Amount outPoint wire.OutPoint - witnessFunc witnessGenerator - + // 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 witnessType + witnessType lnwallet.WitnessType + witnessFunc lnwallet.WitnessGenerator } // incubationRequest is a request to the utxoNursery to incubate a set of @@ -335,7 +297,7 @@ func (u *utxoNursery) IncubateOutputs(closeSummary *lnwallet.ForceCloseSummary) outPoint: closeSummary.SelfOutpoint, blocksToMaturity: closeSummary.SelfOutputMaturity, signDescriptor: closeSummary.SelfOutputSignDesc, - witnessType: commitmentTimeLock, + witnessType: lnwallet.CommitmentTimeLock, } incReq.outputs = append(incReq.outputs, selfOutput) @@ -805,7 +767,7 @@ func fetchGraduatingOutputs(db *channeldb.DB, wallet *lnwallet.LightningWallet, return nil, err } - // If no time-locked outputs can be swept at this point, ten we can + // If no time-locked outputs can be swept at this point, then we can // exit early. if len(results) == 0 { return nil, nil @@ -1050,8 +1012,7 @@ func serializeKidOutput(w io.Writer, kid *kidOutput) error { return err } - serializedPubKey := kid.signDescriptor.PubKey.SerializeCompressed() - if err := wire.WriteVarBytes(w, 0, serializedPubKey); err != nil { + if err := lnwallet.WriteSignDescriptor(w, kid.signDescriptor); err != nil { return err } @@ -1107,7 +1068,7 @@ func deserializeKidOutput(r io.Reader) (*kidOutput, error) { if _, err := r.Read(scratch[:2]); err != nil { return nil, err } - kid.witnessType = witnessType(byteOrder.Uint16(scratch[:2])) + kid.witnessType = lnwallet.WitnessType(byteOrder.Uint16(scratch[:2])) kid.signDescriptor = &lnwallet.SignDescriptor{} @@ -1143,7 +1104,6 @@ func deserializeKidOutput(r io.Reader) (*kidOutput, error) { if _, err := r.Read(scratch[:4]); err != nil { return nil, err } - kid.signDescriptor.HashType = txscript.SigHashType(byteOrder.Uint32(scratch[:4])) return kid, nil } From cbc2179cf8337e6bf7173961c6818db2d7dc8296 Mon Sep 17 00:00:00 2001 From: Philip Hayes Date: Sun, 7 May 2017 04:09:22 -0700 Subject: [PATCH 03/15] breacharbiter: add retribution state persistence This commit adds a breached contract retribution storage layer using boltdb to the breach arbiter. The breach arbiter now stores retribution state on disk between detecting a contract breach, broadcasting a justice transaction that sweeps the channel, and finally witnessing the justice transaction confirm on the blockchain. It is critical that such state is persisted on disk, so that if our node restarts at any point during the retribution procedure, we can recover and continue from the persisted state. --- breacharbiter.go | 601 +++++++++++++++++++++++++++++++----------- breacharbiter_test.go | 398 ++++++++++++++++++++++++++++ lnd_test.go | 99 +++++-- 3 files changed, 916 insertions(+), 182 deletions(-) create mode 100644 breacharbiter_test.go diff --git a/breacharbiter.go b/breacharbiter.go index 859c56be4886..feec1edf4993 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -1,21 +1,35 @@ package main import ( + "bytes" + "encoding/binary" + "errors" "fmt" + "io" "sync" "sync/atomic" + "github.com/boltdb/bolt" "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) +// retributionBucket stores retribution state on disk between detecting a +// contract breach, broadcasting a justice transaction that sweeps the channel, +// and finally witnessing the justice transaction confirm on the blockchain. It +// is critical that such state is persisted on disk, so that if our node +// restarts at any point during the retribution procedure, we can recover and +// continue from the persisted state. +var retributionBucket = []byte("ret") + // breachArbiter is a special subsystem which is responsible for watching and // acting on the detection of any attempted uncooperative channel breaches by // channel counterparties. This file essentially acts as deterrence code for @@ -25,12 +39,11 @@ import ( // counterparties. // TODO(roasbeef): closures in config for subsystem pointers to decouple? type breachArbiter struct { - wallet *lnwallet.LightningWallet - db *channeldb.DB - notifier chainntnfs.ChainNotifier - chainIO lnwallet.BlockChainIO - estimator lnwallet.FeeEstimator - htlcSwitch *htlcswitch.Switch + wallet *lnwallet.LightningWallet + notifier chainntnfs.ChainNotifier + htlcSwitch *htlcSwitch + db *channeldb.DB + retributionStore *retributionStore // breachObservers is a map which tracks all the active breach // observers we're currently managing. The key of the map is the @@ -44,7 +57,7 @@ type breachArbiter struct { // struct to send the necessary information required to punish a // counterparty once a channel breach is detected. Breach observers // use this to communicate with the main contractObserver goroutine. - breachedContracts chan *retributionInfo + breachedContracts chan *retribution // newContracts is a channel which is used by outside subsystems to // notify the breachArbiter of a new contract (a channel) that should @@ -70,15 +83,14 @@ func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB, chain lnwallet.BlockChainIO, fe lnwallet.FeeEstimator) *breachArbiter { return &breachArbiter{ - wallet: wallet, - db: db, - notifier: notifier, - chainIO: chain, - htlcSwitch: h, - estimator: fe, + wallet: wallet, + notifier: notifier, + htlcSwitch: h, + db: db, + retributionStore: newRetributionStore(db), breachObservers: make(map[wire.OutPoint]chan struct{}), - breachedContracts: make(chan *retributionInfo), + breachedContracts: make(chan *retribution), newContracts: make(chan *lnwallet.LightningChannel), settledContracts: make(chan *wire.OutPoint), quit: make(chan struct{}), @@ -94,7 +106,31 @@ func (b *breachArbiter) Start() error { brarLog.Tracef("Starting breach arbiter") - // First we need to query that database state for all currently active + // We load any pending retributions from the database. For each retribution + // we need to restart the retribution procedure to claim our just reward. + err := b.retributionStore.ForAll(func(ret *retribution) error { + // Register for a notification when the breach transaction is confirmed + // on chain. + breachTXID := &ret.commitHash + confChan, err := b.notifier.RegisterConfirmationsNtfn(breachTXID, 1) + if err != nil { + brarLog.Errorf("unable to register for conf updates for txid: "+ + "%v, err: %v", breachTXID, err) + return err + } + + // Launch a new goroutine which to finalize the channel retribution + // after the breach transaction confirms. + b.wg.Add(1) + go b.exactRetribution(confChan, ret) + + return nil + }) + if err != nil { + return err + } + + // We need to query that database state for all currently active // channels, each of these channels will need a goroutine assigned to // it to watch for channel breaches. activeChannels, err := b.db.FetchAllChannels() @@ -248,19 +284,24 @@ out: breachTXID, 1, uint32(currentHeight), ) if err != nil { - brarLog.Errorf("unable to register for conf for txid: %v", - breachTXID) + brarLog.Errorf("unable to register for conf updates for txid: "+ + "%v, err: %v", breachTXID, err) continue } - brarLog.Warnf("A channel has been breached with tx: %v. "+ + brarLog.Warnf("A channel has been breached with txid: %v. "+ "Waiting for confirmation, then justice will be served!", breachTXID) - // With the notification registered, we launch a new - // goroutine which will finalize the channel - // retribution after the breach transaction has been - // confirmed. + // Persist the pending retribution state to disk. + if err := b.retributionStore.Add(breachInfo); err != nil { + brarLog.Errorf("unable to persist breach info to db: %v", err) + continue + } + + // With the notification registered and retribution state persisted, + // we launch a new goroutine which will finalize the channel + // retribution after the breach transaction has been confirmed. b.wg.Add(1) go b.exactRetribution(confChan, breachInfo) @@ -322,108 +363,6 @@ out: return } -// exactRetribution is a goroutine which is executed once a contract breach has -// been detected by a breachObserver. This function is responsible for -// punishing a counterparty for violating the channel contract by sweeping ALL -// the lingering funds within the channel into the daemon's wallet. -// -// NOTE: This MUST be run as a goroutine. -func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, - breachInfo *retributionInfo) { - - defer b.wg.Done() - - // TODO(roasbeef): state needs to be checkpointed here - - select { - case _, ok := <-confChan.Confirmed: - // If the second value is !ok, then the channel has been closed - // signifying a daemon shutdown, so we exit. - if !ok { - return - } - - // Otherwise, if this is a real confirmation notification, then - // we fall through to complete our duty. - case <-b.quit: - return - } - - brarLog.Debugf("Breach transaction %v has been confirmed, sweeping "+ - "revoked funds", breachInfo.commitHash) - - // With the breach transaction confirmed, we now create the justice tx - // which will claim ALL the funds within the channel. - justiceTx, err := b.createJusticeTx(breachInfo) - if err != nil { - brarLog.Errorf("unable to create justice tx: %v", err) - return - } - - brarLog.Debugf("Broadcasting justice tx: %v", newLogClosure(func() string { - return spew.Sdump(justiceTx) - })) - - _, currentHeight, err := b.chainIO.GetBestBlock() - if err != nil { - brarLog.Errorf("unable to get current height: %v", err) - return - } - - // Finally, broadcast the transaction, finalizing the channels' - // retribution against the cheating counterparty. - if err := b.wallet.PublishTransaction(justiceTx); err != nil { - brarLog.Errorf("unable to broadcast "+ - "justice tx: %v", err) - return - } - - // As a conclusionary step, we register for a notification to be - // dispatched once the justice tx is confirmed. After confirmation we - // notify the caller that initiated the retribution workflow that the - // deed has been done. - justiceTXID := justiceTx.TxHash() - confChan, err = b.notifier.RegisterConfirmationsNtfn(&justiceTXID, 1, - uint32(currentHeight)) - if err != nil { - brarLog.Errorf("unable to register for conf for txid: %v", - justiceTXID) - return - } - - select { - case _, ok := <-confChan.Confirmed: - if !ok { - return - } - - // TODO(roasbeef): factor in HTLCs - revokedFunds := breachInfo.revokedOutput.amt - totalFunds := revokedFunds + breachInfo.selfOutput.amt - - brarLog.Infof("Justice for ChannelPoint(%v) has "+ - "been served, %v revoked funds (%v total) "+ - "have been claimed", breachInfo.chanPoint, - revokedFunds, totalFunds) - - // With the channel closed, mark it in the database as such. - err := b.db.MarkChanFullyClosed(&breachInfo.chanPoint) - if err != nil { - brarLog.Errorf("unable to mark chan as closed: %v", err) - } - - // TODO(roasbeef): add peer to blacklist? - - // TODO(roasbeef): close other active channels with offending peer - - close(breachInfo.doneChan) - - return - case <-b.quit: - return - } -} - // breachObserver notifies the breachArbiter contract observer goroutine that a // channel's contract has been breached by the prior counterparty. Once // notified the breachArbiter will attempt to sweep ALL funds within the @@ -559,26 +498,28 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, return lnwallet.CommitSpendRevoke(b.wallet.Cfg.Signer, &desc, tx) } - // Finally, with the two witness generation funcs created, we - // send the retribution information to the utxo nursery. + // Finally, we send the retribution information into the breachArbiter + // event loop to deal swift justice. // TODO(roasbeef): populate htlc breaches - b.breachedContracts <- &retributionInfo{ + b.breachedContracts <- &retribution{ commitHash: breachInfo.BreachTransaction.TxHash(), chanPoint: *chanPoint, selfOutput: &breachedOutput{ - amt: btcutil.Amount(localSignDesc.Output.Value), - outpoint: breachInfo.LocalOutpoint, - witnessFunc: localWitness, + amt: btcutil.Amount(localSignDesc.Output.Value), + outpoint: breachInfo.LocalOutpoint, + signDescriptor: localSignDesc, + witnessType: localWitnessType, }, revokedOutput: &breachedOutput{ - amt: btcutil.Amount(remoteSignDesc.Output.Value), - outpoint: breachInfo.RemoteOutpoint, - witnessFunc: remoteWitness, + amt: btcutil.Amount(remoteSignDesc.Output.Value), + outpoint: breachInfo.RemoteOutpoint, + signDescriptor: remoteSignDesc, + witnessType: remoteWitnessType, }, - doneChan: make(chan struct{}), + htlcOutputs: []*breachedOutput{}, } case <-b.quit: @@ -586,40 +527,114 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, } } -// breachedOutput contains all the information needed to sweep a breached -// output. A breached output is an output that we are now entitled to due to a -// revoked commitment transaction being broadcast. -type breachedOutput struct { - amt btcutil.Amount - outpoint wire.OutPoint - witnessFunc witnessGenerator +// exactRetribution is a goroutine which is executed once a contract breach has +// been detected by a breachObserver. This function is responsible for +// punishing a counterparty for violating the channel contract by sweeping ALL +// the lingering funds within the channel into the daemon's wallet. +// +// NOTE: This MUST be run as a goroutine. +func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, + breachInfo *retributionInfo) { - twoStageClaim bool -} + defer b.wg.Done() -// retributionInfo encapsulates all the data needed to sweep all the contested -// funds within a channel whose contract has been breached by the prior -// counterparty. This struct is used by the utxoNursery to create the justice -// transaction which spends all outputs of the commitment transaction into an -// output controlled by the wallet. -type retributionInfo struct { - commitHash chainhash.Hash - chanPoint wire.OutPoint + // TODO(roasbeef): state needs to be checkpointed here + + select { + case _, ok := <-confChan.Confirmed: + // If the second value is !ok, then the channel has been closed + // signifying a daemon shutdown, so we exit. + if !ok { + return + } - selfOutput *breachedOutput + // Otherwise, if this is a real confirmation notification, then + // we fall through to complete our duty. + case <-b.quit: + return + } - revokedOutput *breachedOutput + brarLog.Debugf("Breach transaction %v has been confirmed, sweeping "+ + "revoked funds", breachInfo.commitHash) + + // With the breach transaction confirmed, we now create the justice tx + // which will claim ALL the funds within the channel. + justiceTx, err := b.createJusticeTx(breachInfo) + if err != nil { + brarLog.Errorf("unable to create justice tx: %v", err) + return + } + + brarLog.Debugf("Broadcasting justice tx: %v", newLogClosure(func() string { + return spew.Sdump(justiceTx) + })) - htlcOutputs *[]breachedOutput + _, currentHeight, err := b.chainIO.GetBestBlock() + if err != nil { + brarLog.Errorf("unable to get current height: %v", err) + return + } - doneChan chan struct{} + // Finally, broadcast the transaction, finalizing the channels' + // retribution against the cheating counterparty. + if err := b.wallet.PublishTransaction(justiceTx); err != nil { + brarLog.Errorf("unable to broadcast "+ + "justice tx: %v", err) + return + } + + // As a conclusionary step, we register for a notification to be + // dispatched once the justice tx is confirmed. After confirmation we + // notify the caller that initiated the retribution workflow that the + // deed has been done. + justiceTXID := justiceTx.TxHash() + confChan, err = b.notifier.RegisterConfirmationsNtfn(&justiceTXID, 1, + uint32(currentHeight)) + if err != nil { + brarLog.Errorf("unable to register for conf for txid: %v", + justiceTXID) + return + } + + select { + case _, ok := <-confChan.Confirmed: + if !ok { + return + } + + // TODO(roasbeef): factor in HTLCs + revokedFunds := breachInfo.revokedOutput.amt + totalFunds := revokedFunds + breachInfo.selfOutput.amt + + brarLog.Infof("Justice for ChannelPoint(%v) has "+ + "been served, %v revoked funds (%v total) "+ + "have been claimed", breachInfo.chanPoint, + revokedFunds, totalFunds) + + // With the channel closed, mark it in the database as such. + err := b.db.MarkChanFullyClosed(&breachInfo.chanPoint) + if err != nil { + brarLog.Errorf("unable to mark chan as closed: %v", err) + } + + // TODO(roasbeef): add peer to blacklist? + + // TODO(roasbeef): close other active channels with offending peer + + close(breachInfo.doneChan) + + return + case <-b.quit: + return + } } // createJusticeTx creates a transaction which exacts "justice" by sweeping ALL // the funds within the channel which we are now entitled to due to a breach of // the channel's contract by the counterparty. This function returns a *fully* // signed transaction with the witness for each input fully in place. -func (b *breachArbiter) createJusticeTx(r *retributionInfo) (*wire.MsgTx, error) { +func (b *breachArbiter) createJusticeTx(r *retribution) (*wire.MsgTx, error) { + // First, we obtain a new public key script from the wallet which we'll // sweep the funds to. // TODO(roasbeef): possibly create many outputs to minimize change in @@ -656,13 +671,17 @@ func (b *breachArbiter) createJusticeTx(r *retributionInfo) (*wire.MsgTx, error) // witnesses for both commitment outputs, and all the pending HTLCs at // this state in the channel's history. // TODO(roasbeef): handle the 2-layer HTLCs - localWitness, err := r.selfOutput.witnessFunc(justiceTx, hashCache, 0) + localWitnessFunc := r.selfOutput.witnessType.GenWitnessFunc( + &b.wallet.Signer, r.selfOutput.signDescriptor) + localWitness, err := localWitnessFunc(justiceTx, hashCache, 0) if err != nil { return nil, err } justiceTx.TxIn[0].Witness = localWitness - remoteWitness, err := r.revokedOutput.witnessFunc(justiceTx, hashCache, 1) + remoteWitnessFunc := r.revokedOutput.witnessType.GenWitnessFunc( + &b.wallet.Signer, r.revokedOutput.signDescriptor) + remoteWitness, err := remoteWitnessFunc(justiceTx, hashCache, 1) if err != nil { return nil, err } @@ -736,3 +755,269 @@ func (b *breachArbiter) craftCommitSweepTx(closeInfo *lnwallet.UnilateralCloseSu return sweepTx, nil } + +// breachedOutput contains all the information needed to sweep a breached +// output. A breached output is an output that we are now entitled to due to a +// revoked commitment transaction being broadcast. +type breachedOutput struct { + amt btcutil.Amount + outpoint wire.OutPoint + + signDescriptor *lnwallet.SignDescriptor + witnessType lnwallet.WitnessType + + twoStageClaim bool +} + +// retribution encapsulates all the data needed to sweep all the contested +// funds within a channel whose contract has been breached by the prior +// counterparty. This struct is used to create the justice transaction which +// spends all outputs of the commitment transaction into an output controlled +// by the wallet. +type retribution struct { + commitHash chainhash.Hash + chanPoint wire.OutPoint + + selfOutput *breachedOutput + revokedOutput *breachedOutput + htlcOutputs []*breachedOutput +} + +// retributionStore handles persistence of retribution states to disk and is +// backed by a boltdb bucket. The primary responsibility of the retribution +// store is to ensure that we can recover from a restart in the middle of a +// breached contract retribution. +type retributionStore struct { + db *channeldb.DB +} + +// newRetributionStore creates a new instance of a retributionStore. +func newRetributionStore(db *channeldb.DB) *retributionStore { + return &retributionStore{ + db: db, + } +} + +// Add adds a retribution state to the retributionStore, which is then persisted +// to disk. +func (rs *retributionStore) Add(ret *retribution) error { + return rs.db.Update(func(tx *bolt.Tx) error { + // If this is our first contract breach, the retributionBucket won't + // exist, in which case, we just create a new bucket. + retBucket, err := tx.CreateBucketIfNotExists(retributionBucket) + if err != nil { + return err + } + + var outBuf bytes.Buffer + if err := lnwire.WriteOutPoint(&outBuf, &ret.chanPoint); err != nil { + return err + } + + var retBuf bytes.Buffer + if err := ret.Encode(&retBuf); err != nil { + return err + } + + if err := retBucket.Put(outBuf.Bytes(), retBuf.Bytes()); err != nil { + return err + } + + return nil + }) +} + +// Remove removes a retribution state from the retributionStore database. +func (rs *retributionStore) Remove(key *wire.OutPoint) error { + return rs.db.Update(func(tx *bolt.Tx) error { + retBucket := tx.Bucket(retributionBucket) + + // We return an error if the bucket is not already created, since normal + // operation of the breach arbiter should never try to remove a + // finalized retribution state that is not already stored in the db. + if retBucket == nil { + return errors.New("unable to remove retribution because the " + + "db bucket doesn't exist.") + } + + var outBuf bytes.Buffer + if err := lnwire.WriteOutPoint(&outBuf, key); err != nil { + return err + } + + if err := retBucket.Delete(outBuf.Bytes()); err != nil { + return err + } + + return nil + }) +} + +// ForAll iterates through all stored retributions and executes the passed +// callback function on each retribution. +func (rs *retributionStore) ForAll(cb func(*retribution) error) error { + return rs.db.View(func(tx *bolt.Tx) error { + // If the bucket does not exist, then there are no pending retributions. + retBucket := tx.Bucket(retributionBucket) + if retBucket == nil { + return nil + } + + // Otherwise, we fetch each serialized retribution info, deserialize + // it, and execute the passed in callback function on it. + return retBucket.ForEach(func(outBytes, retBytes []byte) error { + ret := &retribution{} + if err := ret.Decode(bytes.NewBuffer(retBytes)); err != nil { + return err + } + + return cb(ret) + }) + }) +} + +// Encode serializes the retribution into the passed byte stream. +func (ret *retribution) Encode(w io.Writer) error { + if _, err := w.Write(ret.commitHash[:]); err != nil { + return err + } + + if err := lnwire.WriteOutPoint(w, &ret.chanPoint); err != nil { + return err + } + + if err := ret.selfOutput.Encode(w); err != nil { + return err + } + + if err := ret.revokedOutput.Encode(w); err != nil { + return err + } + + numHtlcOutputs := len(ret.htlcOutputs) + if err := wire.WriteVarInt(w, 0, uint64(numHtlcOutputs)); err != nil { + return err + } + + for i := 0; i < numHtlcOutputs; i++ { + if err := ret.htlcOutputs[i].Encode(w); err != nil { + return err + } + } + + return nil +} + +// Dencode deserializes a retribution from the passed byte stream. +func (ret *retribution) Decode(r io.Reader) error { + var scratch [32]byte + + if _, err := io.ReadFull(r, scratch[:]); err != nil { + return err + } + hash, err := chainhash.NewHash(scratch[:]) + if err != nil { + return err + } + ret.commitHash = *hash + + if err := lnwire.ReadOutPoint(r, &ret.chanPoint); err != nil { + return err + } + + ret.selfOutput = &breachedOutput{} + if err := ret.selfOutput.Decode(r); err != nil { + return err + } + + ret.revokedOutput = &breachedOutput{} + if err := ret.revokedOutput.Decode(r); err != nil { + return err + } + + numHtlcOutputsU64, err := wire.ReadVarInt(r, 0) + if err != nil { + return err + } + numHtlcOutputs := int(numHtlcOutputsU64) + + ret.htlcOutputs = make([]*breachedOutput, numHtlcOutputs) + for i := 0; i < numHtlcOutputs; i++ { + ret.htlcOutputs[i] = &breachedOutput{} + if err := ret.htlcOutputs[i].Decode(r); err != nil { + return err + } + } + + return nil +} + +// Encode serializes a breachedOutput into the passed byte stream. +func (bo *breachedOutput) Encode(w io.Writer) error { + var scratch [8]byte + + binary.BigEndian.PutUint64(scratch[:8], uint64(bo.amt)) + if _, err := w.Write(scratch[:8]); err != nil { + return err + } + + if err := lnwire.WriteOutPoint(w, &bo.outpoint); err != nil { + return err + } + + if err := lnwallet.WriteSignDescriptor(w, bo.signDescriptor); err != nil { + return err + } + + binary.BigEndian.PutUint16(scratch[:2], uint16(bo.witnessType)) + if _, err := w.Write(scratch[:2]); err != nil { + return err + } + + if bo.twoStageClaim { + scratch[0] = 1 + } else { + scratch[0] = 0 + } + if _, err := w.Write(scratch[:1]); err != nil { + return err + } + + return nil +} + +// Decode deserializes a breachedOutput from the passed byte stream. +func (bo *breachedOutput) Decode(r io.Reader) error { + var scratch [8]byte + + if _, err := io.ReadFull(r, scratch[:8]); err != nil { + return err + } + bo.amt = btcutil.Amount(binary.BigEndian.Uint64(scratch[:8])) + + if err := lnwire.ReadOutPoint(r, &bo.outpoint); err != nil { + return err + } + + signDescriptor := lnwallet.SignDescriptor{} + if err := lnwallet.ReadSignDescriptor(r, &signDescriptor); err != nil { + return err + } + bo.signDescriptor = &signDescriptor + + if _, err := io.ReadFull(r, scratch[:2]); err != nil { + return err + } + bo.witnessType = lnwallet.WitnessType(binary.BigEndian.Uint16(scratch[:2])) + + if _, err := io.ReadFull(r, scratch[:1]); err != nil { + return err + } + if scratch[0] == 1 { + bo.twoStageClaim = true + } else { + bo.twoStageClaim = false + } + + return nil +} diff --git a/breacharbiter_test.go b/breacharbiter_test.go new file mode 100644 index 000000000000..92034b0710ec --- /dev/null +++ b/breacharbiter_test.go @@ -0,0 +1,398 @@ +package main + +import ( + "bytes" + "errors" + "fmt" + "io/ioutil" + "os" + "reflect" + "testing" + + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnwallet" + "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/chaincfg/chainhash" + "github.com/roasbeef/btcd/txscript" + "github.com/roasbeef/btcd/wire" + "github.com/roasbeef/btcutil" +) + +var ( + breachOutPoints = []wire.OutPoint{ + { + Hash: [chainhash.HashSize]byte{ + 0x51, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, + 0x48, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, + 0x2d, 0xe7, 0x93, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, + 0x1f, 0xb, 0x4c, 0xf9, 0x9e, 0xc5, 0x8c, 0xe9, + }, + Index: 9, + }, + { + Hash: [chainhash.HashSize]byte{ + 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, + 0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4, + 0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9, + 0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, + }, + Index: 49, + }, + { + Hash: [chainhash.HashSize]byte{ + 0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, + 0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, + 0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, + 0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9, + }, + Index: 23, + }, + } + + breachKeys = [][]byte{ + {0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, + 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, + 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, + 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, + 0xea, 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, + 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, + 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, + 0xb4, 0x12, 0xa3, + }, + {0x07, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a, + 0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc, 0x1e, + 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48, 0x2e, 0xca, + 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a, 0x5c, 0xb2, 0xe0, + 0xea, 0xdd, 0xfb, 0x84, 0xcc, 0xf9, 0x74, 0x44, 0x64, + 0xf8, 0x2e, 0x16, 0x0b, 0xfa, 0x9b, 0x8b, 0x64, 0xf9, + 0xd4, 0xc0, 0x3f, 0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, + 0xb4, 0x12, 0xa3, + }, + {0x02, 0xce, 0x0b, 0x14, 0xfb, 0x84, 0x2b, 0x1b, + 0xa5, 0x49, 0xfd, 0xd6, 0x75, 0xc9, 0x80, 0x75, 0xf1, + 0x2e, 0x9c, 0x51, 0x0f, 0x8e, 0xf5, 0x2b, 0xd0, 0x21, + 0xa9, 0xa1, 0xf4, 0x80, 0x9d, 0x3b, 0x4d, + }, + } + + breachSignDescs = []lnwallet.SignDescriptor{ + { + PrivateTweak: []byte{ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, + }, + WitnessScript: []byte{ + 0x00, 0x14, 0xee, 0x91, 0x41, 0x7e, 0x85, 0x6c, 0xde, + 0x10, 0xa2, 0x91, 0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2, + 0xef, 0xb5, 0x71, 0x48, + }, + Output: &wire.TxOut{ + Value: 5000000000, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + HashType: txscript.SigHashAll, + }, + { + PrivateTweak: []byte{ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, + }, + WitnessScript: []byte{ + 0x00, 0x14, 0xee, 0x91, 0x41, 0x7e, 0x85, 0x6c, 0xde, + 0x10, 0xa2, 0x91, 0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2, + 0xef, 0xb5, 0x71, 0x48, + }, + Output: &wire.TxOut{ + Value: 5000000000, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + HashType: txscript.SigHashAll, + }, + { + PrivateTweak: []byte{ + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, + }, + WitnessScript: []byte{ + 0x00, 0x14, 0xee, 0x91, 0x41, 0x7e, 0x85, 0x6c, 0xde, + 0x10, 0xa2, 0x91, 0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2, + 0xef, 0xb5, 0x71, 0x48, + }, + Output: &wire.TxOut{ + Value: 5000000000, + PkScript: []byte{ + 0x41, // OP_DATA_65 + 0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5, + 0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42, + 0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1, + 0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24, + 0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97, + 0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78, + 0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20, + 0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63, + 0xa6, // 65-byte signature + 0xac, // OP_CHECKSIG + }, + }, + HashType: txscript.SigHashAll, + }, + } + + breachedOutputs = []breachedOutput{ + { + amt: btcutil.Amount(1e7), + outpoint: breachOutPoints[0], + witnessType: lnwallet.CommitmentNoDelay, + twoStageClaim: true, + }, + + { + amt: btcutil.Amount(2e9), + outpoint: breachOutPoints[1], + witnessType: lnwallet.CommitmentRevoke, + twoStageClaim: false, + }, + + { + amt: btcutil.Amount(3e4), + outpoint: breachOutPoints[2], + witnessType: lnwallet.CommitmentDelayOutput, + twoStageClaim: false, + }, + } + + retributions = []retribution{ + { + commitHash: [chainhash.HashSize]byte{ + 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, + 0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4, + 0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9, + 0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, + }, + chanPoint: breachOutPoints[0], + selfOutput: &breachedOutputs[0], + revokedOutput: &breachedOutputs[1], + htlcOutputs: []*breachedOutput{}, + }, + { + commitHash: [chainhash.HashSize]byte{ + 0x51, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, + 0x48, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, + 0x2d, 0xe7, 0x93, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, + 0x1f, 0xb, 0x4c, 0xf9, 0x9e, 0xc5, 0x8c, 0xe9, + }, + chanPoint: breachOutPoints[1], + selfOutput: &breachedOutputs[0], + revokedOutput: &breachedOutputs[1], + htlcOutputs: []*breachedOutput{ + &breachedOutputs[1], + &breachedOutputs[2], + }, + }, + } +) + +// Parse the pubkeys in the breached outputs. +func initBreachedOutputs() error { + for i := 0; i < len(breachedOutputs); i++ { + bo := &breachedOutputs[i] + + // Parse the sign descriptor's pubkey. + sd := &breachSignDescs[i] + pubkey, err := btcec.ParsePubKey(breachKeys[i], btcec.S256()) + if err != nil { + fmt.Errorf("unable to parse pubkey: %v", breachKeys[i]) + } + sd.PubKey = pubkey + bo.signDescriptor = sd + } + + return nil +} + +// Test that breachedOutput Encode/Decode works. +func TestBreachedOutputSerialization(t *testing.T) { + if err := initBreachedOutputs(); err != nil { + t.Fatalf("unable to init breached outputs: %v", err) + } + + for i := 0; i < len(breachedOutputs); i++ { + bo := &breachedOutputs[i] + + var buf bytes.Buffer + + if err := bo.Encode(&buf); err != nil { + t.Fatalf("unable to serialize breached output [%v]: %v", i, err) + } + + desBo := &breachedOutput{} + if err := desBo.Decode(&buf); err != nil { + t.Fatalf("unable to deserialize breached output [%v]: %v", i, err) + } + + if !reflect.DeepEqual(bo, desBo) { + t.Fatalf("original and deserialized breached outputs not equal:\n"+ + "original : %+v\n"+ + "deserialized : %+v\n", + bo, desBo) + } + } +} + +// Test that retribution Encode/Decode works. +func TestRetributionSerialization(t *testing.T) { + if err := initBreachedOutputs(); err != nil { + t.Fatalf("unable to init breached outputs: %v", err) + } + + for i := 0; i < len(retributions); i++ { + ret := &retributions[i] + + var buf bytes.Buffer + + if err := ret.Encode(&buf); err != nil { + t.Fatalf("unable to serialize retribution [%v]: %v", i, err) + } + + desRet := &retribution{} + if err := desRet.Decode(&buf); err != nil { + t.Fatalf("unable to deserialize retribution [%v]: %v", i, err) + } + + if !reflect.DeepEqual(ret, desRet) { + t.Fatalf("original and deserialized retribution infos not equal:\n"+ + "original : %+v\n"+ + "deserialized : %+v\n", + ret, desRet) + } + } +} + +// TODO(phlip9): reuse existing function? +// makeTestDB creates a new instance of the ChannelDB for testing purposes. A +// callback which cleans up the created temporary directories is also returned +// and intended to be executed after the test completes. +func makeTestDB() (*channeldb.DB, func(), error) { + var db *channeldb.DB = nil + + // First, create a temporary directory to be used for the duration of + // this test. + tempDirName, err := ioutil.TempDir("", "channeldb") + if err != nil { + return nil, nil, err + } + + // Next, create channeldb for the first time. + db, err = channeldb.Open(tempDirName) + if err != nil { + return nil, nil, err + } + + cleanUp := func() { + if db != nil { + db.Close() + } + os.RemoveAll(tempDirName) + } + + return db, cleanUp, nil +} + +func countRetributions(t *testing.T, rs *retributionStore) int { + count := 0 + err := rs.ForAll(func(_ *retribution) error { + count += 1 + return nil + }) + if err != nil { + t.Fatalf("unable to list retributions in db: %v", err) + } + return count +} + +// Test that the retribution persistence layer works. +func TestRetributionStore(t *testing.T) { + db, cleanUp, err := makeTestDB() + defer cleanUp() + if err != nil { + t.Fatalf("unable to create test db: %v", err) + } + + if err := initBreachedOutputs(); err != nil { + t.Fatalf("unable to init breached outputs: %v", err) + } + + rs := newRetributionStore(db) + + // Make sure that a new retribution store is actually emtpy. + if count := countRetributions(t, rs); count != 0 { + t.Fatalf("expected 0 retributions, found %v", count) + } + + // Add some retribution states to the store. + if err := rs.Add(&retributions[0]); err != nil { + t.Fatalf("unable to add to retribution store: %v", err) + } + if err := rs.Add(&retributions[1]); err != nil { + t.Fatalf("unable to add to retribution store: %v", err) + } + + // There should be 2 retributions in the store. + if count := countRetributions(t, rs); count != 2 { + t.Fatalf("expected 2 retributions, found %v", count) + } + + // Retrieving the retribution states from the store should yield the same + // values as the originals. + rs.ForAll(func(ret *retribution) error { + equal0 := reflect.DeepEqual(ret, &retributions[0]) + equal1 := reflect.DeepEqual(ret, &retributions[1]) + if !equal0 || !equal1 { + return errors.New("unexpected retribution retrieved from db") + } + return nil + }) + + // Remove the retribution states. + if err := rs.Remove(&retributions[0].chanPoint); err != nil { + t.Fatalf("unable to remove from retribution store: %v", err) + } + if err := rs.Remove(&retributions[1].chanPoint); err != nil { + t.Fatalf("unable to remove from retribution store: %v", err) + } + + // Ensure that the retribution store is empty again. + if count := countRetributions(t, rs); count != 0 { + t.Fatalf("expected 0 retributions, found %v", count) + } +} diff --git a/lnd_test.go b/lnd_test.go index 5d7a81005a22..fe86aa02f23d 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -1720,7 +1720,35 @@ func copyFile(dest, src string) error { } return d.Close() +} + +func waitForTxInMempool(miner *btcrpcclient.Client, + timeout time.Duration) (*chainhash.Hash, error) { + + var txid *chainhash.Hash + breakTimeout := time.After(timeout) + ticker := time.NewTicker(50 * time.Millisecond) + defer ticker.Stop() +poll: + for { + select { + case <-breakTimeout: + return nil, errors.New("no tx found in mempool") + case <-ticker.C: + mempool, err := miner.GetRawMempool() + if err != nil { + return nil, err + } + + if len(mempool) == 0 { + continue + } + txid = mempool[0] + break poll + } + } + return txid, nil } func testRevokedCloseRetribution(net *networkHarness, t *harnessTest) { @@ -1883,34 +1911,48 @@ func testRevokedCloseRetribution(net *networkHarness, t *harnessTest) { // broadcasting his current channel state. This is actually the // commitment transaction of a prior *revoked* state, so he'll soon // feel the wrath of Alice's retribution. - breachTXID := closeChannelAndAssert(ctxb, t, net, net.Bob, chanPoint, - true) + force := true + closeUpdates, _, err := net.CloseChannel(ctxb, net.Bob, chanPoint, force) + if err != nil { + t.Fatalf("unable to close channel: %v", err) + } - // Query the mempool for Alice's justice transaction, this should be - // broadcast as Bob's contract breaching transaction gets confirmed - // above. - var justiceTXID *chainhash.Hash - breakTimeout := time.After(time.Second * 5) -poll: - for { - select { - case <-breakTimeout: - t.Fatalf("justice tx not found in mempool") - default: - } + // Wait for Bob's breach transaction to show up in the mempool to ensure + // that Alice's node has started waiting for confirmations. + _, err = waitForTxInMempool(net.Miner.Node, 5*time.Second) + if err != nil { + t.Fatalf("unable to find Bob's breach tx in mempool: %v", err) + } + time.Sleep(100 * time.Millisecond) - mempool, err := net.Miner.Node.GetRawMempool() - if err != nil { - t.Fatalf("unable to get mempool: %v", err) - } + // Here, Alice sees Bob's breach transaction in the mempool, but is waiting + // for it to confirm before continuing her retribution. We restart Alice to + // ensure that she is persisting her retribution state and continues + // watching for the breach transaction to confirm even after her node + // restarts. + if err := net.RestartNode(net.Alice, nil); err != nil { + t.Fatalf("unable to restart Alice's node: %v", err) + } - if len(mempool) == 0 { - continue - } + // Finally, generate a single block, wait for the final close status + // update, then ensure that the closing transaction was included in the + // block. + block := mineBlocks(t, net, 1)[0] - justiceTXID = mempool[0] - break poll + breachTXID, err := net.WaitForChannelClose(ctxb, closeUpdates) + if err != nil { + t.Fatalf("error while waiting for channel close: %v", err) + } + assertTxInBlock(t, block, breachTXID) + + // Query the mempool for Alice's justice transaction, this should be + // broadcast as Bob's contract breaching transaction gets confirmed + // above. + justiceTXID, err := waitForTxInMempool(net.Miner.Node, 5*time.Second) + if err != nil { + t.Fatalf("unable to find Alice's justice tx in mempool: %v", err) } + time.Sleep(100 * time.Millisecond) // Query for the mempool transaction found above. Then assert that all // the inputs of this transaction are spending outputs generated by @@ -1926,9 +1968,18 @@ poll: } } + // We restart Alice here to ensure that she persists her retribution state + // and successfully continues exacting retribution after restarting. At + // this point, Alice has broadcast the justice transaction, but it hasn't + // been confirmed yet; when Alice restarts, she should start waiting for + // the justice transaction to confirm again. + if err := net.RestartNode(net.Alice, nil); err != nil { + t.Fatalf("unable to restart Alice's node: %v", err) + } + // Now mine a block, this transaction should include Alice's justice // transaction which was just accepted into the mempool. - block := mineBlocks(t, net, 1)[0] + block = mineBlocks(t, net, 1)[0] // The block should have exactly *two* transactions, one of which is // the justice transaction. From 5d6d81b792a1d87a8592732307da64ba2f86ea96 Mon Sep 17 00:00:00 2001 From: Philip Hayes Date: Sun, 7 May 2017 04:43:55 -0700 Subject: [PATCH 04/15] utxonursery: finish migrating to lnwire OutPoint serialization --- breacharbiter.go | 9 ++++++++- utxonursery.go | 14 ++++++++------ utxonursery_test.go | 6 +++--- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index feec1edf4993..12572955caa2 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -612,11 +612,18 @@ func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, revokedFunds, totalFunds) // With the channel closed, mark it in the database as such. - err := b.db.MarkChanFullyClosed(&breachInfo.chanPoint) + err := b.db.MarkChanFullyClosed(&ret.chanPoint) if err != nil { brarLog.Errorf("unable to mark chan as closed: %v", err) } + // Justice has been carried out; we can safely delete the retribution + // info from the database. + err = b.retributionStore.Remove(&ret.chanPoint) + if err != nil { + brarLog.Errorf("unable to remove retribution from the db: %v", err) + } + // TODO(roasbeef): add peer to blacklist? // TODO(roasbeef): close other active channels with offending peer diff --git a/utxonursery.go b/utxonursery.go index 09cf2b5de8b1..63baaba86497 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -441,7 +441,7 @@ func (u *utxoNursery) NurseryReport(chanPoint *wire.OutPoint) (*contractMaturity } var b bytes.Buffer - if err := writeOutpoint(&b, chanPoint); err != nil { + if err := lnwire.WriteOutPoint(&b, chanPoint); err != nil { return err } chanPointBytes := b.Bytes() @@ -563,7 +563,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 = lnwire.WriteOutPoint(&b, &k.originChanPoint) if err != nil { return err } @@ -604,7 +604,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 := lnwire.WriteOutPoint(&originPoint, &k.originChanPoint); err != nil { return err } @@ -916,7 +916,7 @@ func deleteGraduatedOutputs(db *channeldb.DB, deleteHeight uint32) error { } for _, sweptOutput := range sweptOutputs { var chanPoint bytes.Buffer - err := writeOutpoint(&chanPoint, &sweptOutput.originChanPoint) + err := lnwire.WriteOutPoint(&chanPoint, &sweptOutput.originChanPoint) if err != nil { return err } @@ -993,7 +993,7 @@ func serializeKidOutput(w io.Writer, kid *kidOutput) error { if err := lnwire.WriteOutPoint(w, &kid.outPoint); err != nil { return err } - if err := writeOutpoint(w, &kid.originChanPoint); err != nil { + if err := lnwire.WriteOutPoint(w, &kid.originChanPoint); err != nil { return err } @@ -1051,7 +1051,9 @@ func deserializeKidOutput(r io.Reader) (*kidOutput, error) { if err != nil { return nil, err } - if err := readOutpoint(io.LimitReader(r, 40), &kid.originChanPoint); err != nil { + + err = lnwire.ReadOutPoint(io.LimitReader(r, 40), &kid.originChanPoint) + if err != nil { return nil, err } diff --git a/utxonursery_test.go b/utxonursery_test.go index 613eadbcb21e..e4062ec309ef 100644 --- a/utxonursery_test.go +++ b/utxonursery_test.go @@ -171,7 +171,7 @@ var ( amt: btcutil.Amount(13e7), outPoint: outPoints[0], blocksToMaturity: uint32(100), - witnessType: commitmentTimeLock, + witnessType: lnwallet.CommitmentTimeLock, confHeight: uint32(1770001), }, @@ -180,7 +180,7 @@ var ( amt: btcutil.Amount(24e7), outPoint: outPoints[1], blocksToMaturity: uint32(50), - witnessType: commitmentTimeLock, + witnessType: lnwallet.CommitmentTimeLock, confHeight: uint32(22342321), }, @@ -189,7 +189,7 @@ var ( amt: btcutil.Amount(2e5), outPoint: outPoints[2], blocksToMaturity: uint32(12), - witnessType: commitmentTimeLock, + witnessType: lnwallet.CommitmentTimeLock, confHeight: uint32(34241), }, } From 766d5184f1e10e68ba37043b9b73aa3d18fb3c53 Mon Sep 17 00:00:00 2001 From: Philip Hayes Date: Sun, 7 May 2017 14:58:53 -0700 Subject: [PATCH 05/15] lint: fix linter complaints --- breacharbiter_test.go | 6 +++--- lnwallet/witnessgen.go | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/breacharbiter_test.go b/breacharbiter_test.go index 92034b0710ec..4ab151937a00 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -231,7 +231,7 @@ func initBreachedOutputs() error { sd := &breachSignDescs[i] pubkey, err := btcec.ParsePubKey(breachKeys[i], btcec.S256()) if err != nil { - fmt.Errorf("unable to parse pubkey: %v", breachKeys[i]) + return fmt.Errorf("unable to parse pubkey: %v", breachKeys[i]) } sd.PubKey = pubkey bo.signDescriptor = sd @@ -303,7 +303,7 @@ func TestRetributionSerialization(t *testing.T) { // callback which cleans up the created temporary directories is also returned // and intended to be executed after the test completes. func makeTestDB() (*channeldb.DB, func(), error) { - var db *channeldb.DB = nil + var db *channeldb.DB // First, create a temporary directory to be used for the duration of // this test. @@ -331,7 +331,7 @@ func makeTestDB() (*channeldb.DB, func(), error) { func countRetributions(t *testing.T, rs *retributionStore) int { count := 0 err := rs.ForAll(func(_ *retribution) error { - count += 1 + count++ return nil }) if err != nil { diff --git a/lnwallet/witnessgen.go b/lnwallet/witnessgen.go index 77fd6995a802..42e92fc37507 100644 --- a/lnwallet/witnessgen.go +++ b/lnwallet/witnessgen.go @@ -13,16 +13,17 @@ import ( type WitnessType uint16 const ( - // Witness that allows us to spend the output of a commitment transaction - // after a relative lock-time lockout. + // CommitmentTimeLock is a witness that allows us to spend the output of a + // commitment transaction after a relative lock-time lockout. CommitmentTimeLock WitnessType = 0 - // Witness that allows us to spend a settled no-delay output immediately on - // a counterparty's commitment transaction. + // CommitmentNoDelay is a witness that allows us to spend a settled no-delay + // output immediately on a counterparty's commitment transaction. CommitmentNoDelay WitnessType = 1 - // Witness that allows us to sweep the settled output of a malicious - // counterparty's who broadcasts a revoked commitment transaction. + // CommitmentRevoke is a witness that allows us to sweep the settled output + // of a malicious counterparty's who broadcasts a revoked commitment + // transaction. CommitmentRevoke WitnessType = 2 ) From 4e7ee1db44c986a2ede26282079711f2f866d587 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 25 Jul 2017 18:19:44 -0700 Subject: [PATCH 06/15] breacharbiter+channeldb: resolves rebase conflicts --- breacharbiter.go | 31 ++++++++++-------- channeldb/channel.go | 76 +++++++++++++++++++++++++++++++------------- 2 files changed, 72 insertions(+), 35 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index 12572955caa2..1f6a5f64b1a8 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -40,9 +40,11 @@ var retributionBucket = []byte("ret") // TODO(roasbeef): closures in config for subsystem pointers to decouple? type breachArbiter struct { wallet *lnwallet.LightningWallet - notifier chainntnfs.ChainNotifier - htlcSwitch *htlcSwitch db *channeldb.DB + notifier chainntnfs.ChainNotifier + htlcSwitch *htlcswitch.Switch + chainIO lnwallet.BlockChainIO + estimator lnwallet.FeeEstimator retributionStore *retributionStore // breachObservers is a map which tracks all the active breach @@ -106,13 +108,20 @@ func (b *breachArbiter) Start() error { brarLog.Tracef("Starting breach arbiter") + // TODO(roasbeef): instead use closure height of channel + _, currentHeight, err := b.chainIO.GetBestBlock() + if err != nil { + return err + } + // We load any pending retributions from the database. For each retribution // we need to restart the retribution procedure to claim our just reward. - err := b.retributionStore.ForAll(func(ret *retribution) error { + err = b.retributionStore.ForAll(func(ret *retribution) error { // Register for a notification when the breach transaction is confirmed // on chain. breachTXID := &ret.commitHash - confChan, err := b.notifier.RegisterConfirmationsNtfn(breachTXID, 1) + confChan, err := b.notifier.RegisterConfirmationsNtfn(breachTXID, 1, + uint32(currentHeight)) if err != nil { brarLog.Errorf("unable to register for conf updates for txid: "+ "%v, err: %v", breachTXID, err) @@ -162,12 +171,6 @@ func (b *breachArbiter) Start() error { b.wg.Add(1) go b.contractObserver(channelsToWatch) - // TODO(roasbeef): instead use closure height of channel - _, currentHeight, err := b.chainIO.GetBestBlock() - if err != nil { - return err - } - // Additionally, we'll also want to retrieve any pending close or force // close transactions to we can properly mark them as resolved in the // database. @@ -534,7 +537,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, // // NOTE: This MUST be run as a goroutine. func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, - breachInfo *retributionInfo) { + breachInfo *retribution) { defer b.wg.Done() @@ -612,14 +615,14 @@ func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, revokedFunds, totalFunds) // With the channel closed, mark it in the database as such. - err := b.db.MarkChanFullyClosed(&ret.chanPoint) + err := b.db.MarkChanFullyClosed(&breachInfo.chanPoint) if err != nil { brarLog.Errorf("unable to mark chan as closed: %v", err) } // Justice has been carried out; we can safely delete the retribution // info from the database. - err = b.retributionStore.Remove(&ret.chanPoint) + err = b.retributionStore.Remove(&breachInfo.chanPoint) if err != nil { brarLog.Errorf("unable to remove retribution from the db: %v", err) } @@ -788,6 +791,8 @@ type retribution struct { selfOutput *breachedOutput revokedOutput *breachedOutput htlcOutputs []*breachedOutput + + doneChan chan struct{} } // retributionStore handles persistence of retribution states to disk and is diff --git a/channeldb/channel.go b/channeldb/channel.go index b44375ca303a..47d6341447b1 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -380,7 +380,7 @@ func (c *OpenChannel) fullSync(tx *bolt.Tx) error { return err } var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, c.ChanID); err != nil { + if err := writeOutpoint(&b, c.ChanID); err != nil { return err } if chanIndexBucket.Get(b.Bytes()) == nil { @@ -871,7 +871,7 @@ func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary) error { } var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, c.ChanID); err != nil { + if err := writeOutpoint(&b, c.ChanID); err != nil { return err } @@ -982,7 +982,7 @@ func serializeChannelCloseSummary(w io.Writer, cs *ChannelCloseSummary) error { return err } - if err := lnwire.WriteOutPoint(w, &cs.ChanPoint); err != nil { + if err := writeOutpoint(w, &cs.ChanPoint); err != nil { return err } if _, err := w.Write(cs.ClosingTXID[:]); err != nil { @@ -1037,7 +1037,7 @@ func deserializeCloseChannelSummary(r io.Reader) (*ChannelCloseSummary, error) { return nil, err } - if err := lnwire.ReadOutPoint(r, &c.ChanPoint); err != nil { + if err := readOutpoint(r, &c.ChanPoint); err != nil { return nil, err } if _, err := io.ReadFull(r, c.ClosingTXID[:]); err != nil { @@ -1245,7 +1245,7 @@ func putChanCapacity(openChanBucket *bolt.Bucket, channel *OpenChannel) error { scratch3 := make([]byte, 8) var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1290,7 +1290,7 @@ func deleteChanCapacity(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanCapacity(openChanBucket *bolt.Bucket, channel *OpenChannel) error { // A byte slice re-used to compute each key prefix below. var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1317,7 +1317,7 @@ func putChanFeePerKw(openChanBucket *bolt.Bucket, channel *OpenChannel) error { byteOrder.PutUint64(scratch, uint64(channel.FeePerKw)) var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1337,7 +1337,7 @@ func deleteChanMinFeePerKw(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanMinFeePerKw(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1356,7 +1356,7 @@ func putChanNumUpdates(openChanBucket *bolt.Bucket, channel *OpenChannel) error byteOrder.PutUint64(scratch, channel.NumUpdates) var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1376,7 +1376,7 @@ func deleteChanNumUpdates(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanNumUpdates(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1395,7 +1395,7 @@ func putChanAmountsTransferred(openChanBucket *bolt.Bucket, channel *OpenChannel scratch2 := make([]byte, 8) var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1428,7 +1428,7 @@ func deleteChanAmountsTransferred(openChanBucket *bolt.Bucket, chanID []byte) er func fetchChanAmountsTransferred(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1450,7 +1450,7 @@ func putChanIsPending(openChanBucket *bolt.Bucket, channel *OpenChannel) error { scratch := make([]byte, 2) var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1476,7 +1476,7 @@ func deleteChanIsPending(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanIsPending(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1541,7 +1541,7 @@ func deleteChanConfInfo(openChanBucket *bolt.Bucket, chanID []byte) error { func putChannelIDs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { // TODO(roasbeef): just pass in chanID everywhere for puts var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1568,7 +1568,7 @@ func fetchChannelIDs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { b bytes.Buffer ) - if err = lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err = writeOutpoint(&b, channel.ChanID); err != nil { return err } @@ -1604,7 +1604,7 @@ func putChanCommitFee(openChanBucket *bolt.Bucket, channel *OpenChannel) error { func fetchChanCommitFee(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := lnwire.WriteOutpoint(&b, &channel.FundingOutpoint); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1628,7 +1628,7 @@ func deleteChanCommitFee(openChanBucket *bolt.Bucket, chanID []byte) error { func putChanCommitTxns(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var bc bytes.Buffer - if err := lnwire.WriteOutpoint(&bc, &channel.FundingOutpoint); err != nil { + if err := writeOutpoint(&bc, &channel.FundingOutpoint); err != nil { return err } txnsKey := make([]byte, len(commitTxnsKey)+bc.Len()) @@ -1658,7 +1658,7 @@ func deleteChanCommitTxns(nodeChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanCommitTxns(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var bc bytes.Buffer var err error - if err = lnwire.WriteOutPoint(&bc, channel.ChanID); err != nil { + if err = writeOutpoint(&bc, channel.ChanID); err != nil { return err } txnsKey := make([]byte, len(commitTxnsKey)+bc.Len()) @@ -1745,7 +1745,7 @@ func putChanConfigs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { func fetchChanConfigs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var bc bytes.Buffer - if err := lnwire.WriteOutPoint(&bc, channel.ChanID); err != nil { + if err := writeOutpoint(&bc, channel.ChanID); err != nil { return err } configKey := make([]byte, len(chanConfigPrefix)+len(bc.Bytes())) @@ -1885,7 +1885,7 @@ func deleteChanFundingInfo(nodeChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } fundTxnKey := make([]byte, len(fundingTxnKey)+b.Len()) @@ -1971,7 +1971,7 @@ func deleteChanRevocationState(nodeChanBucket *bolt.Bucket, chanID []byte) error func fetchChanRevocationState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, channel.ChanID); err != nil { return err } preimageKey := make([]byte, len(revocationStateKey)+b.Len()) @@ -2316,6 +2316,38 @@ func wipeChannelLogEntries(log *bolt.Bucket, o *wire.OutPoint) error { return nil } +func writeOutpoint(w io.Writer, o *wire.OutPoint) error { + // TODO(roasbeef): make all scratch buffers on the stack + scratch := make([]byte, 4) + + // TODO(roasbeef): write raw 32 bytes instead of wasting the extra + // byte. + if err := wire.WriteVarBytes(w, 0, o.Hash[:]); err != nil { + return err + } + + byteOrder.PutUint32(scratch, o.Index) + _, err := w.Write(scratch) + return err +} + +func readOutpoint(r io.Reader, o *wire.OutPoint) error { + scratch := make([]byte, 4) + + txid, err := wire.ReadVarBytes(r, 0, 32, "prevout") + if err != nil { + return err + } + copy(o.Hash[:], txid) + + if _, err := r.Read(scratch); err != nil { + return err + } + o.Index = byteOrder.Uint32(scratch) + + return nil +} + func writeBool(w io.Writer, b bool) error { boolByte := byte(0x01) if !b { From e7558119594162597c0602bcc34c708640bd50b4 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 25 Jul 2017 20:14:03 -0700 Subject: [PATCH 07/15] breacharbiter: reverts retributionInfo naming and realign diffs --- breacharbiter.go | 256 ++++++++++++++++++++++++------------------ breacharbiter_test.go | 8 +- 2 files changed, 148 insertions(+), 116 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index 1f6a5f64b1a8..040c3afbca07 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -59,7 +59,7 @@ type breachArbiter struct { // struct to send the necessary information required to punish a // counterparty once a channel breach is detected. Breach observers // use this to communicate with the main contractObserver goroutine. - breachedContracts chan *retribution + breachedContracts chan *retributionInfo // newContracts is a channel which is used by outside subsystems to // notify the breachArbiter of a new contract (a channel) that should @@ -92,7 +92,7 @@ func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB, retributionStore: newRetributionStore(db), breachObservers: make(map[wire.OutPoint]chan struct{}), - breachedContracts: make(chan *retribution), + breachedContracts: make(chan *retributionInfo), newContracts: make(chan *lnwallet.LightningChannel), settledContracts: make(chan *wire.OutPoint), quit: make(chan struct{}), @@ -116,7 +116,7 @@ func (b *breachArbiter) Start() error { // We load any pending retributions from the database. For each retribution // we need to restart the retribution procedure to claim our just reward. - err = b.retributionStore.ForAll(func(ret *retribution) error { + err = b.retributionStore.ForAll(func(ret *retributionInfo) error { // Register for a notification when the breach transaction is confirmed // on chain. breachTXID := &ret.commitHash @@ -366,6 +366,115 @@ out: return } +// exactRetribution is a goroutine which is executed once a contract breach has +// been detected by a breachObserver. This function is responsible for +// punishing a counterparty for violating the channel contract by sweeping ALL +// the lingering funds within the channel into the daemon's wallet. +// +// NOTE: This MUST be run as a goroutine. +func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, + breachInfo *retributionInfo) { + + defer b.wg.Done() + + // TODO(roasbeef): state needs to be checkpointed here + + select { + case _, ok := <-confChan.Confirmed: + // If the second value is !ok, then the channel has been closed + // signifying a daemon shutdown, so we exit. + if !ok { + return + } + + // Otherwise, if this is a real confirmation notification, then + // we fall through to complete our duty. + case <-b.quit: + return + } + + brarLog.Debugf("Breach transaction %v has been confirmed, sweeping "+ + "revoked funds", breachInfo.commitHash) + + // With the breach transaction confirmed, we now create the justice tx + // which will claim ALL the funds within the channel. + justiceTx, err := b.createJusticeTx(breachInfo) + if err != nil { + brarLog.Errorf("unable to create justice tx: %v", err) + return + } + + brarLog.Debugf("Broadcasting justice tx: %v", newLogClosure(func() string { + return spew.Sdump(justiceTx) + })) + + _, currentHeight, err := b.chainIO.GetBestBlock() + if err != nil { + brarLog.Errorf("unable to get current height: %v", err) + return + } + + // Finally, broadcast the transaction, finalizing the channels' + // retribution against the cheating counterparty. + if err := b.wallet.PublishTransaction(justiceTx); err != nil { + brarLog.Errorf("unable to broadcast "+ + "justice tx: %v", err) + return + } + + // As a conclusionary step, we register for a notification to be + // dispatched once the justice tx is confirmed. After confirmation we + // notify the caller that initiated the retribution workflow that the + // deed has been done. + justiceTXID := justiceTx.TxHash() + confChan, err = b.notifier.RegisterConfirmationsNtfn(&justiceTXID, 1, + uint32(currentHeight)) + if err != nil { + brarLog.Errorf("unable to register for conf for txid: %v", + justiceTXID) + return + } + + select { + case _, ok := <-confChan.Confirmed: + if !ok { + return + } + + // TODO(roasbeef): factor in HTLCs + revokedFunds := breachInfo.revokedOutput.amt + totalFunds := revokedFunds + breachInfo.selfOutput.amt + + brarLog.Infof("Justice for ChannelPoint(%v) has "+ + "been served, %v revoked funds (%v total) "+ + "have been claimed", breachInfo.chanPoint, + revokedFunds, totalFunds) + + // With the channel closed, mark it in the database as such. + err := b.db.MarkChanFullyClosed(&breachInfo.chanPoint) + if err != nil { + brarLog.Errorf("unable to mark chan as closed: %v", err) + } + + // Justice has been carried out; we can safely delete the retribution + // info from the database. + err = b.retributionStore.Remove(&breachInfo.chanPoint) + if err != nil { + brarLog.Errorf("unable to remove retribution from the db: %v", err) + } + + // TODO(roasbeef): add peer to blacklist? + + // TODO(roasbeef): close other active channels with offending peer + + close(breachInfo.doneChan) + + return + case <-b.quit: + return + } +} + // breachObserver notifies the breachArbiter contract observer goroutine that a // channel's contract has been breached by the prior counterparty. Once // notified the breachArbiter will attempt to sweep ALL funds within the @@ -504,7 +613,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, // Finally, we send the retribution information into the breachArbiter // event loop to deal swift justice. // TODO(roasbeef): populate htlc breaches - b.breachedContracts <- &retribution{ + b.breachedContracts <- &retributionInfo{ commitHash: breachInfo.BreachTransaction.TxHash(), chanPoint: *chanPoint, @@ -523,6 +632,7 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, }, htlcOutputs: []*breachedOutput{}, + doneChan: make(chan struct{}), } case <-b.quit: @@ -530,120 +640,42 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, } } -// exactRetribution is a goroutine which is executed once a contract breach has -// been detected by a breachObserver. This function is responsible for -// punishing a counterparty for violating the channel contract by sweeping ALL -// the lingering funds within the channel into the daemon's wallet. -// -// NOTE: This MUST be run as a goroutine. -func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, - breachInfo *retribution) { - - defer b.wg.Done() - - // TODO(roasbeef): state needs to be checkpointed here - - select { - case _, ok := <-confChan.Confirmed: - // If the second value is !ok, then the channel has been closed - // signifying a daemon shutdown, so we exit. - if !ok { - return - } - - // Otherwise, if this is a real confirmation notification, then - // we fall through to complete our duty. - case <-b.quit: - return - } - - brarLog.Debugf("Breach transaction %v has been confirmed, sweeping "+ - "revoked funds", breachInfo.commitHash) - - // With the breach transaction confirmed, we now create the justice tx - // which will claim ALL the funds within the channel. - justiceTx, err := b.createJusticeTx(breachInfo) - if err != nil { - brarLog.Errorf("unable to create justice tx: %v", err) - return - } - - brarLog.Debugf("Broadcasting justice tx: %v", newLogClosure(func() string { - return spew.Sdump(justiceTx) - })) - - _, currentHeight, err := b.chainIO.GetBestBlock() - if err != nil { - brarLog.Errorf("unable to get current height: %v", err) - return - } - - // Finally, broadcast the transaction, finalizing the channels' - // retribution against the cheating counterparty. - if err := b.wallet.PublishTransaction(justiceTx); err != nil { - brarLog.Errorf("unable to broadcast "+ - "justice tx: %v", err) - return - } - - // As a conclusionary step, we register for a notification to be - // dispatched once the justice tx is confirmed. After confirmation we - // notify the caller that initiated the retribution workflow that the - // deed has been done. - justiceTXID := justiceTx.TxHash() - confChan, err = b.notifier.RegisterConfirmationsNtfn(&justiceTXID, 1, - uint32(currentHeight)) - if err != nil { - brarLog.Errorf("unable to register for conf for txid: %v", - justiceTXID) - return - } - - select { - case _, ok := <-confChan.Confirmed: - if !ok { - return - } - - // TODO(roasbeef): factor in HTLCs - revokedFunds := breachInfo.revokedOutput.amt - totalFunds := revokedFunds + breachInfo.selfOutput.amt +// breachedOutput contains all the information needed to sweep a breached +// output. A breached output is an output that we are now entitled to due to a +// revoked commitment transaction being broadcast. +type breachedOutput struct { + amt btcutil.Amount + outpoint wire.OutPoint - brarLog.Infof("Justice for ChannelPoint(%v) has "+ - "been served, %v revoked funds (%v total) "+ - "have been claimed", breachInfo.chanPoint, - revokedFunds, totalFunds) + signDescriptor *lnwallet.SignDescriptor + witnessType lnwallet.WitnessType - // With the channel closed, mark it in the database as such. - err := b.db.MarkChanFullyClosed(&breachInfo.chanPoint) - if err != nil { - brarLog.Errorf("unable to mark chan as closed: %v", err) - } + twoStageClaim bool +} - // Justice has been carried out; we can safely delete the retribution - // info from the database. - err = b.retributionStore.Remove(&breachInfo.chanPoint) - if err != nil { - brarLog.Errorf("unable to remove retribution from the db: %v", err) - } +// retributionInfo encapsulates all the data needed to sweep all the contested +// funds within a channel whose contract has been breached by the prior +// counterparty. This struct is used to create the justice transaction which +// spends all outputs of the commitment transaction into an output controlled +// by the wallet. +type retributionInfo struct { + commitHash chainhash.Hash + chanPoint wire.OutPoint - // TODO(roasbeef): add peer to blacklist? + selfOutput *breachedOutput - // TODO(roasbeef): close other active channels with offending peer + revokedOutput *breachedOutput - close(breachInfo.doneChan) + htlcOutputs []*breachedOutput - return - case <-b.quit: - return - } + doneChan chan struct{} } // createJusticeTx creates a transaction which exacts "justice" by sweeping ALL // the funds within the channel which we are now entitled to due to a breach of // the channel's contract by the counterparty. This function returns a *fully* // signed transaction with the witness for each input fully in place. -func (b *breachArbiter) createJusticeTx(r *retribution) (*wire.MsgTx, error) { +func (b *breachArbiter) createJusticeTx(r *retributionInfo) (*wire.MsgTx, error) { // First, we obtain a new public key script from the wallet which we'll // sweep the funds to. @@ -784,7 +816,7 @@ type breachedOutput struct { // counterparty. This struct is used to create the justice transaction which // spends all outputs of the commitment transaction into an output controlled // by the wallet. -type retribution struct { +type retributionInfo struct { commitHash chainhash.Hash chanPoint wire.OutPoint @@ -792,7 +824,7 @@ type retribution struct { revokedOutput *breachedOutput htlcOutputs []*breachedOutput - doneChan chan struct{} + doneChan chan struct{} } // retributionStore handles persistence of retribution states to disk and is @@ -812,7 +844,7 @@ func newRetributionStore(db *channeldb.DB) *retributionStore { // Add adds a retribution state to the retributionStore, which is then persisted // to disk. -func (rs *retributionStore) Add(ret *retribution) error { +func (rs *retributionStore) Add(ret *retributionInfo) error { return rs.db.Update(func(tx *bolt.Tx) error { // If this is our first contract breach, the retributionBucket won't // exist, in which case, we just create a new bucket. @@ -867,7 +899,7 @@ func (rs *retributionStore) Remove(key *wire.OutPoint) error { // ForAll iterates through all stored retributions and executes the passed // callback function on each retribution. -func (rs *retributionStore) ForAll(cb func(*retribution) error) error { +func (rs *retributionStore) ForAll(cb func(*retributionInfo) error) error { return rs.db.View(func(tx *bolt.Tx) error { // If the bucket does not exist, then there are no pending retributions. retBucket := tx.Bucket(retributionBucket) @@ -878,7 +910,7 @@ func (rs *retributionStore) ForAll(cb func(*retribution) error) error { // Otherwise, we fetch each serialized retribution info, deserialize // it, and execute the passed in callback function on it. return retBucket.ForEach(func(outBytes, retBytes []byte) error { - ret := &retribution{} + ret := &retributionInfo{} if err := ret.Decode(bytes.NewBuffer(retBytes)); err != nil { return err } @@ -889,7 +921,7 @@ func (rs *retributionStore) ForAll(cb func(*retribution) error) error { } // Encode serializes the retribution into the passed byte stream. -func (ret *retribution) Encode(w io.Writer) error { +func (ret *retributionInfo) Encode(w io.Writer) error { if _, err := w.Write(ret.commitHash[:]); err != nil { return err } @@ -921,7 +953,7 @@ func (ret *retribution) Encode(w io.Writer) error { } // Dencode deserializes a retribution from the passed byte stream. -func (ret *retribution) Decode(r io.Reader) error { +func (ret *retributionInfo) Decode(r io.Reader) error { var scratch [32]byte if _, err := io.ReadFull(r, scratch[:]); err != nil { diff --git a/breacharbiter_test.go b/breacharbiter_test.go index 4ab151937a00..d2732d8faf28 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -191,7 +191,7 @@ var ( }, } - retributions = []retribution{ + retributions = []retributionInfo{ { commitHash: [chainhash.HashSize]byte{ 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, @@ -284,7 +284,7 @@ func TestRetributionSerialization(t *testing.T) { t.Fatalf("unable to serialize retribution [%v]: %v", i, err) } - desRet := &retribution{} + desRet := &retributionInfo{} if err := desRet.Decode(&buf); err != nil { t.Fatalf("unable to deserialize retribution [%v]: %v", i, err) } @@ -330,7 +330,7 @@ func makeTestDB() (*channeldb.DB, func(), error) { func countRetributions(t *testing.T, rs *retributionStore) int { count := 0 - err := rs.ForAll(func(_ *retribution) error { + err := rs.ForAll(func(_ *retributionInfo) error { count++ return nil }) @@ -374,7 +374,7 @@ func TestRetributionStore(t *testing.T) { // Retrieving the retribution states from the store should yield the same // values as the originals. - rs.ForAll(func(ret *retribution) error { + rs.ForAll(func(ret *retributionInfo) error { equal0 := reflect.DeepEqual(ret, &retributions[0]) equal1 := reflect.DeepEqual(ret, &retributions[1]) if !equal0 || !equal1 { From 2df5cfd49b84a6498807f1db2c8cfebf1633df6a Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 25 Jul 2017 20:39:59 -0700 Subject: [PATCH 08/15] lnw+ba+utxon+cdb: reverts OutPoint and TxOut encoding --- breacharbiter.go | 14 +++---- channeldb/channel.go | 36 ++++++++--------- channeldb/db.go | 6 +-- channeldb/graph.go | 14 +++---- lnwire/lnwire.go | 40 ++++++++++++++++++- lnwire/outpoint.go | 59 ---------------------------- lnwire/outpoint_test.go | 40 ------------------- utxonursery.go | 87 +++++++++++++++++++++++++++++++++++------ 8 files changed, 148 insertions(+), 148 deletions(-) delete mode 100644 lnwire/outpoint.go delete mode 100644 lnwire/outpoint_test.go diff --git a/breacharbiter.go b/breacharbiter.go index 040c3afbca07..4fa713e6e417 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -15,7 +15,6 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwallet" - "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" @@ -676,7 +675,6 @@ type retributionInfo struct { // the channel's contract by the counterparty. This function returns a *fully* // signed transaction with the witness for each input fully in place. func (b *breachArbiter) createJusticeTx(r *retributionInfo) (*wire.MsgTx, error) { - // First, we obtain a new public key script from the wallet which we'll // sweep the funds to. // TODO(roasbeef): possibly create many outputs to minimize change in @@ -854,7 +852,7 @@ func (rs *retributionStore) Add(ret *retributionInfo) error { } var outBuf bytes.Buffer - if err := lnwire.WriteOutPoint(&outBuf, &ret.chanPoint); err != nil { + if err := writeOutpoint(&outBuf, &ret.chanPoint); err != nil { return err } @@ -885,7 +883,7 @@ func (rs *retributionStore) Remove(key *wire.OutPoint) error { } var outBuf bytes.Buffer - if err := lnwire.WriteOutPoint(&outBuf, key); err != nil { + if err := writeOutpoint(&outBuf, key); err != nil { return err } @@ -926,7 +924,7 @@ func (ret *retributionInfo) Encode(w io.Writer) error { return err } - if err := lnwire.WriteOutPoint(w, &ret.chanPoint); err != nil { + if err := writeOutpoint(w, &ret.chanPoint); err != nil { return err } @@ -965,7 +963,7 @@ func (ret *retributionInfo) Decode(r io.Reader) error { } ret.commitHash = *hash - if err := lnwire.ReadOutPoint(r, &ret.chanPoint); err != nil { + if err := readOutpoint(r, &ret.chanPoint); err != nil { return err } @@ -1005,7 +1003,7 @@ func (bo *breachedOutput) Encode(w io.Writer) error { return err } - if err := lnwire.WriteOutPoint(w, &bo.outpoint); err != nil { + if err := writeOutpoint(w, &bo.outpoint); err != nil { return err } @@ -1039,7 +1037,7 @@ func (bo *breachedOutput) Decode(r io.Reader) error { } bo.amt = btcutil.Amount(binary.BigEndian.Uint64(scratch[:8])) - if err := lnwire.ReadOutPoint(r, &bo.outpoint); err != nil { + if err := readOutpoint(r, &bo.outpoint); err != nil { return err } diff --git a/channeldb/channel.go b/channeldb/channel.go index 47d6341447b1..6640db305a1b 100644 --- a/channeldb/channel.go +++ b/channeldb/channel.go @@ -380,7 +380,7 @@ func (c *OpenChannel) fullSync(tx *bolt.Tx) error { return err } var b bytes.Buffer - if err := writeOutpoint(&b, c.ChanID); err != nil { + if err := writeOutpoint(&b, &c.FundingOutpoint); err != nil { return err } if chanIndexBucket.Get(b.Bytes()) == nil { @@ -871,7 +871,7 @@ func (c *OpenChannel) CloseChannel(summary *ChannelCloseSummary) error { } var b bytes.Buffer - if err := writeOutpoint(&b, c.ChanID); err != nil { + if err := writeOutpoint(&b, &c.FundingOutpoint); err != nil { return err } @@ -1245,7 +1245,7 @@ func putChanCapacity(openChanBucket *bolt.Bucket, channel *OpenChannel) error { scratch3 := make([]byte, 8) var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1290,7 +1290,7 @@ func deleteChanCapacity(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanCapacity(openChanBucket *bolt.Bucket, channel *OpenChannel) error { // A byte slice re-used to compute each key prefix below. var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1317,7 +1317,7 @@ func putChanFeePerKw(openChanBucket *bolt.Bucket, channel *OpenChannel) error { byteOrder.PutUint64(scratch, uint64(channel.FeePerKw)) var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1337,7 +1337,7 @@ func deleteChanMinFeePerKw(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanMinFeePerKw(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1356,7 +1356,7 @@ func putChanNumUpdates(openChanBucket *bolt.Bucket, channel *OpenChannel) error byteOrder.PutUint64(scratch, channel.NumUpdates) var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1376,7 +1376,7 @@ func deleteChanNumUpdates(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanNumUpdates(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1395,7 +1395,7 @@ func putChanAmountsTransferred(openChanBucket *bolt.Bucket, channel *OpenChannel scratch2 := make([]byte, 8) var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1428,7 +1428,7 @@ func deleteChanAmountsTransferred(openChanBucket *bolt.Bucket, chanID []byte) er func fetchChanAmountsTransferred(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1450,7 +1450,7 @@ func putChanIsPending(openChanBucket *bolt.Bucket, channel *OpenChannel) error { scratch := make([]byte, 2) var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1476,7 +1476,7 @@ func deleteChanIsPending(openChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanIsPending(openChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1541,7 +1541,7 @@ func deleteChanConfInfo(openChanBucket *bolt.Bucket, chanID []byte) error { func putChannelIDs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { // TODO(roasbeef): just pass in chanID everywhere for puts var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1568,7 +1568,7 @@ func fetchChannelIDs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { b bytes.Buffer ) - if err = writeOutpoint(&b, channel.ChanID); err != nil { + if err = writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } @@ -1658,7 +1658,7 @@ func deleteChanCommitTxns(nodeChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanCommitTxns(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var bc bytes.Buffer var err error - if err = writeOutpoint(&bc, channel.ChanID); err != nil { + if err = writeOutpoint(&bc, &channel.FundingOutpoint); err != nil { return err } txnsKey := make([]byte, len(commitTxnsKey)+bc.Len()) @@ -1745,7 +1745,7 @@ func putChanConfigs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { func fetchChanConfigs(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var bc bytes.Buffer - if err := writeOutpoint(&bc, channel.ChanID); err != nil { + if err := writeOutpoint(&bc, &channel.FundingOutpoint); err != nil { return err } configKey := make([]byte, len(chanConfigPrefix)+len(bc.Bytes())) @@ -1885,7 +1885,7 @@ func deleteChanFundingInfo(nodeChanBucket *bolt.Bucket, chanID []byte) error { func fetchChanFundingInfo(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } fundTxnKey := make([]byte, len(fundingTxnKey)+b.Len()) @@ -1971,7 +1971,7 @@ func deleteChanRevocationState(nodeChanBucket *bolt.Bucket, chanID []byte) error func fetchChanRevocationState(nodeChanBucket *bolt.Bucket, channel *OpenChannel) error { var b bytes.Buffer - if err := writeOutpoint(&b, channel.ChanID); err != nil { + if err := writeOutpoint(&b, &channel.FundingOutpoint); err != nil { return err } preimageKey := make([]byte, len(revocationStateKey)+b.Len()) diff --git a/channeldb/db.go b/channeldb/db.go index 8a3c84d3ff20..59c77f04fa4d 100644 --- a/channeldb/db.go +++ b/channeldb/db.go @@ -269,7 +269,7 @@ func (d *DB) fetchNodeChannels(openChanBucket, outBytes := bytes.NewReader(k) chanID := &wire.OutPoint{} - if err := lnwire.ReadOutPoint(outBytes, chanID); err != nil { + if err := readOutpoint(outBytes, chanID); err != nil { return err } @@ -373,7 +373,7 @@ func (d *DB) MarkChannelAsOpen(outpoint *wire.OutPoint, // Generate the database key, which will consist of the // IsPending prefix followed by the channel's outpoint. var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, outpoint); err != nil { + if err := writeOutpoint(&b, outpoint); err != nil { return err } keyPrefix := make([]byte, 3+b.Len()) @@ -455,7 +455,7 @@ func (d *DB) FetchClosedChannels(pendingOnly bool) ([]*ChannelCloseSummary, erro func (d *DB) MarkChanFullyClosed(chanPoint *wire.OutPoint) error { return d.Update(func(tx *bolt.Tx) error { var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, chanPoint); err != nil { + if err := writeOutpoint(&b, chanPoint); err != nil { return err } diff --git a/channeldb/graph.go b/channeldb/graph.go index a8e95364cbdd..711f817f5e7e 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -456,7 +456,7 @@ func (c *ChannelGraph) AddChannelEdge(edge *ChannelEdgeInfo) error { // Finally we add it to the channel index which maps channel // points (outpoints) to the shorter channel ID's. var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, &edge.ChannelPoint); err != nil { + if err := writeOutpoint(&b, &edge.ChannelPoint); err != nil { return err } return chanIndex.Put(b.Bytes(), chanKey[:]) @@ -600,7 +600,7 @@ func (c *ChannelGraph) PruneGraph(spentOutputs []*wire.OutPoint, // if NOT if filter var opBytes bytes.Buffer - if err := lnwire.WriteOutPoint(&opBytes, chanPoint); err != nil { + if err := writeOutpoint(&opBytes, chanPoint); err != nil { return nil } @@ -724,7 +724,7 @@ func (c *ChannelGraph) ChannelID(chanPoint *wire.OutPoint) (uint64, error) { var chanID uint64 var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, chanPoint); err != nil { + if err := writeOutpoint(&b, chanPoint); err != nil { return 0, nil } @@ -756,7 +756,7 @@ func (c *ChannelGraph) ChannelID(chanPoint *wire.OutPoint) (uint64, error) { func delChannelByEdge(edges *bolt.Bucket, edgeIndex *bolt.Bucket, chanIndex *bolt.Bucket, chanPoint *wire.OutPoint) error { var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, chanPoint); err != nil { + if err := writeOutpoint(&b, chanPoint); err != nil { return err } @@ -1271,7 +1271,7 @@ func (c *ChannelGraph) FetchChannelEdgesByOutpoint(op *wire.OutPoint) (*ChannelE return err } var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, op); err != nil { + if err := writeOutpoint(&b, op); err != nil { return err } chanID := chanIndex.Get(b.Bytes()) @@ -1692,7 +1692,7 @@ func putChanEdgeInfo(edgeIndex *bolt.Bucket, edgeInfo *ChannelEdgeInfo, chanID [ return err } - if err := lnwire.WriteOutPoint(&b, &edgeInfo.ChannelPoint); err != nil { + if err := writeOutpoint(&b, &edgeInfo.ChannelPoint); err != nil { return err } if err := binary.Write(&b, byteOrder, uint64(edgeInfo.Capacity)); err != nil { @@ -1794,7 +1794,7 @@ func deserializeChanEdgeInfo(r io.Reader) (*ChannelEdgeInfo, error) { } edgeInfo.ChannelPoint = wire.OutPoint{} - if err := lnwire.ReadOutPoint(r, &edgeInfo.ChannelPoint); err != nil { + if err := readOutpoint(r, &edgeInfo.ChannelPoint); err != nil { return nil, err } if err := binary.Read(r, byteOrder, &edgeInfo.Capacity); err != nil { diff --git a/lnwire/lnwire.go b/lnwire/lnwire.go index 98bc10423dd3..529e878258f2 100644 --- a/lnwire/lnwire.go +++ b/lnwire/lnwire.go @@ -4,11 +4,13 @@ import ( "encoding/binary" "fmt" "io" + "math" "net" "github.com/go-errors/errors" "github.com/roasbeef/btcd/btcec" + "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) @@ -187,7 +189,21 @@ func writeElement(w io.Writer, element interface{}) error { } case wire.OutPoint: - if err := WriteOutPoint(w, &e); err != nil { + var h [32]byte + copy(h[:], e.Hash[:]) + if _, err := w.Write(h[:]); err != nil { + return err + } + + if e.Index > math.MaxUint16 { + return fmt.Errorf("index for outpoint (%v) is "+ + "greater than max index of %v", e.Index, + math.MaxUint16) + } + + var idx [2]byte + binary.BigEndian.PutUint16(idx[:], uint16(e.Index)) + if _, err := w.Write(idx[:]); err != nil { return err } @@ -463,10 +479,30 @@ func readElement(r io.Reader, element interface{}) error { } *e = pkScript case *wire.OutPoint: - if err := ReadOutPoint(r, e); err != nil { + var h [32]byte + if _, err = io.ReadFull(r, h[:]); err != nil { + return err + } + hash, err := chainhash.NewHash(h[:]) + if err != nil { return err } + var idxBytes [2]byte + _, err = io.ReadFull(r, idxBytes[:]) + if err != nil { + return err + } + index := binary.BigEndian.Uint16(idxBytes[:]) + + *e = wire.OutPoint{ + Hash: *hash, + Index: uint32(index), + } + case *FailCode: + if err := readElement(r, (*uint16)(e)); err != nil { + return err + } case *ChannelID: if _, err := io.ReadFull(r, e[:]); err != nil { return err diff --git a/lnwire/outpoint.go b/lnwire/outpoint.go deleted file mode 100644 index b990f7f017ed..000000000000 --- a/lnwire/outpoint.go +++ /dev/null @@ -1,59 +0,0 @@ -package lnwire - -import ( - "encoding/binary" - "fmt" - "io" - "math" - - "github.com/roasbeef/btcd/chaincfg/chainhash" - "github.com/roasbeef/btcd/wire" -) - -// WriteOutPoint serializes a wire.OutPoint struct into the passed io.Writer -// stream. -func WriteOutPoint(w io.Writer, o *wire.OutPoint) error { - if _, err := w.Write(o.Hash[:chainhash.HashSize]); err != nil { - return err - } - - if o.Index > math.MaxUint16 { - return fmt.Errorf("index for outpoint (%v) is "+ - "greater than max index of %v", o.Index, math.MaxUint16) - } - - var idx [2]byte - binary.BigEndian.PutUint16(idx[:], uint16(o.Index)) - if _, err := w.Write(idx[:]); err != nil { - return err - } - - return nil -} - -// ReadOutPoint deserializes a wire.OutPoint struct from the passed io.Reader -// stream. -func ReadOutPoint(r io.Reader, o *wire.OutPoint) error { - var h [chainhash.HashSize]byte - if _, err := io.ReadFull(r, h[:]); err != nil { - return err - } - hash, err := chainhash.NewHash(h[:]) - if err != nil { - return err - } - - var idxBytes [2]byte - _, err = io.ReadFull(r, idxBytes[:]) - if err != nil { - return err - } - index := binary.BigEndian.Uint16(idxBytes[:]) - - *o = wire.OutPoint{ - Hash: *hash, - Index: uint32(index), - } - - return nil -} diff --git a/lnwire/outpoint_test.go b/lnwire/outpoint_test.go deleted file mode 100644 index 9e33e9d21ded..000000000000 --- a/lnwire/outpoint_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package lnwire - -import ( - "bytes" - "reflect" - "testing" - - "github.com/roasbeef/btcd/chaincfg/chainhash" - "github.com/roasbeef/btcd/wire" -) - -func TestOutpointSerialization(t *testing.T) { - outpoint := wire.OutPoint{ - Hash: [chainhash.HashSize]byte{ - 0x51, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda, - 0x48, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17, - 0x2d, 0xe7, 0x93, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, - 0x1f, 0xb, 0x4c, 0xf9, 0x9e, 0xc5, 0x8c, 0xe9, - }, - Index: 9, - } - - var buf bytes.Buffer - - if err := WriteOutPoint(&buf, &outpoint); err != nil { - t.Fatalf("unable to serialize outpoint: %v", err) - } - - var deserializedOutpoint wire.OutPoint - if err := ReadOutPoint(&buf, &deserializedOutpoint); err != nil { - t.Fatalf("unable to deserialize outpoint: %v", err) - } - - if !reflect.DeepEqual(outpoint, deserializedOutpoint) { - t.Fatalf("original and deserialized outpoints are different:\n"+ - "original : %+v\n"+ - "deserialized : %+v\n", - outpoint, deserializedOutpoint) - } -} diff --git a/utxonursery.go b/utxonursery.go index 63baaba86497..b4a1b196c27d 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -14,7 +14,6 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwallet" - "github.com/lightningnetwork/lnd/lnwire" "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" @@ -441,7 +440,7 @@ func (u *utxoNursery) NurseryReport(chanPoint *wire.OutPoint) (*contractMaturity } var b bytes.Buffer - if err := lnwire.WriteOutPoint(&b, chanPoint); err != nil { + if err := writeOutpoint(&b, chanPoint); err != nil { return err } chanPointBytes := b.Bytes() @@ -547,7 +546,7 @@ 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 := lnwire.WriteOutPoint(&outpointBytes, &k.outPoint); err != nil { + if err := writeOutpoint(&outpointBytes, &k.outPoint); err != nil { return err } var kidBytes bytes.Buffer @@ -563,7 +562,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 = lnwire.WriteOutPoint(&b, &k.originChanPoint) + err = writeOutpoint(&b, &k.originChanPoint) if err != nil { return err } @@ -604,7 +603,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 := lnwire.WriteOutPoint(&originPoint, &k.originChanPoint); err != nil { + if err := writeOutpoint(&originPoint, &k.originChanPoint); err != nil { return err } @@ -621,7 +620,7 @@ 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 := lnwire.WriteOutPoint(&outpointBytes, &k.outPoint); err != nil { + if err := writeOutpoint(&outpointBytes, &k.outPoint); err != nil { return err } if err := psclBucket.Delete(outpointBytes.Bytes()); err != nil { @@ -916,7 +915,7 @@ func deleteGraduatedOutputs(db *channeldb.DB, deleteHeight uint32) error { } for _, sweptOutput := range sweptOutputs { var chanPoint bytes.Buffer - err := lnwire.WriteOutPoint(&chanPoint, &sweptOutput.originChanPoint) + err := writeOutpoint(&chanPoint, &sweptOutput.originChanPoint) if err != nil { return err } @@ -990,10 +989,10 @@ func serializeKidOutput(w io.Writer, kid *kidOutput) error { return err } - if err := lnwire.WriteOutPoint(w, &kid.outPoint); err != nil { + if err := writeOutpoint(w, &kid.outPoint); err != nil { return err } - if err := lnwire.WriteOutPoint(w, &kid.originChanPoint); err != nil { + if err := writeOutpoint(w, &kid.originChanPoint); err != nil { return err } @@ -1047,12 +1046,12 @@ func deserializeKidOutput(r io.Reader) (*kidOutput, error) { } kid.amt = btcutil.Amount(byteOrder.Uint64(scratch[:])) - err := lnwire.ReadOutPoint(io.LimitReader(r, 40), &kid.outPoint) + err := readOutpoint(io.LimitReader(r, 40), &kid.outPoint) if err != nil { return nil, err } - err = lnwire.ReadOutPoint(io.LimitReader(r, 40), &kid.originChanPoint) + err = readOutpoint(io.LimitReader(r, 40), &kid.originChanPoint) if err != nil { return nil, err } @@ -1109,3 +1108,69 @@ func deserializeKidOutput(r io.Reader) (*kidOutput, error) { return kid, nil } + +// TODO(bvu): copied from channeldb, remove repetition +func writeOutpoint(w io.Writer, o *wire.OutPoint) error { + // TODO(roasbeef): make all scratch buffers on the stack + scratch := make([]byte, 4) + + // TODO(roasbeef): write raw 32 bytes instead of wasting the extra + // byte. + if err := wire.WriteVarBytes(w, 0, o.Hash[:]); err != nil { + return err + } + + byteOrder.PutUint32(scratch, o.Index) + _, err := w.Write(scratch) + return err +} + +// TODO(bvu): copied from channeldb, remove repetition +func readOutpoint(r io.Reader, o *wire.OutPoint) error { + scratch := make([]byte, 4) + + txid, err := wire.ReadVarBytes(r, 0, 32, "prevout") + if err != nil { + return err + } + copy(o.Hash[:], txid) + + if _, err := r.Read(scratch); err != nil { + return err + } + o.Index = byteOrder.Uint32(scratch) + + return nil +} + +func writeTxOut(w io.Writer, txo *wire.TxOut) error { + scratch := make([]byte, 8) + + byteOrder.PutUint64(scratch, uint64(txo.Value)) + if _, err := w.Write(scratch); err != nil { + return err + } + + if err := wire.WriteVarBytes(w, 0, txo.PkScript); err != nil { + return err + } + + return nil +} + +func readTxOut(r io.Reader, txo *wire.TxOut) error { + scratch := make([]byte, 8) + + if _, err := r.Read(scratch); err != nil { + return err + } + txo.Value = int64(byteOrder.Uint64(scratch)) + + pkScript, err := wire.ReadVarBytes(r, 0, 80, "pkScript") + if err != nil { + return err + } + txo.PkScript = pkScript + + return nil +} From ce705c9dce6daa9f3391d25e5486482015ff83b3 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Tue, 25 Jul 2017 22:57:29 -0700 Subject: [PATCH 09/15] breacharbiter: adds persistence to retribution flow This commit introduces a RetributionStore interface, which establishes the methods used to access persisted information regarding breached channels. A RetributionStore is used to persist retributionInfo regarding all channels for which the wallet has signaled a breach. The current design could be improved by moving certain functionality, e.g. closing channels and htlc links, such that they are handled by upstream by their respective subsystems. This was investigated, but deemed preferable to postpone to a later update to prevent the current implementation from sprawling amongst too many packages. The test suite creates a mockRetributionStore and ensures that it exhibits the same behavior as the retribution store backed by a channeldb.DB. --- breacharbiter.go | 568 +++++++++++++++++++++++++++++------------- breacharbiter_test.go | 162 +++++++++--- 2 files changed, 516 insertions(+), 214 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index 4fa713e6e417..ffad00adc91f 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -15,6 +15,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwallet" + "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/wire" @@ -27,7 +28,7 @@ import ( // is critical that such state is persisted on disk, so that if our node // restarts at any point during the retribution procedure, we can recover and // continue from the persisted state. -var retributionBucket = []byte("ret") +var retributionBucket = []byte("retribution") // breachArbiter is a special subsystem which is responsible for watching and // acting on the detection of any attempted uncooperative channel breaches by @@ -38,13 +39,14 @@ var retributionBucket = []byte("ret") // counterparties. // TODO(roasbeef): closures in config for subsystem pointers to decouple? type breachArbiter struct { - wallet *lnwallet.LightningWallet - db *channeldb.DB - notifier chainntnfs.ChainNotifier - htlcSwitch *htlcswitch.Switch - chainIO lnwallet.BlockChainIO - estimator lnwallet.FeeEstimator - retributionStore *retributionStore + wallet *lnwallet.LightningWallet + db *channeldb.DB + notifier chainntnfs.ChainNotifier + chainIO lnwallet.BlockChainIO + estimator lnwallet.FeeEstimator + htlcSwitch *htlcswitch.Switch + + retributionStore RetributionStore // breachObservers is a map which tracks all the active breach // observers we're currently managing. The key of the map is the @@ -84,10 +86,13 @@ func newBreachArbiter(wallet *lnwallet.LightningWallet, db *channeldb.DB, chain lnwallet.BlockChainIO, fe lnwallet.FeeEstimator) *breachArbiter { return &breachArbiter{ - wallet: wallet, - notifier: notifier, - htlcSwitch: h, - db: db, + wallet: wallet, + db: db, + notifier: notifier, + chainIO: chain, + htlcSwitch: h, + estimator: fe, + retributionStore: newRetributionStore(db), breachObservers: make(map[wire.OutPoint]chan struct{}), @@ -107,30 +112,30 @@ func (b *breachArbiter) Start() error { brarLog.Tracef("Starting breach arbiter") - // TODO(roasbeef): instead use closure height of channel - _, currentHeight, err := b.chainIO.GetBestBlock() - if err != nil { - return err - } - - // We load any pending retributions from the database. For each retribution - // we need to restart the retribution procedure to claim our just reward. - err = b.retributionStore.ForAll(func(ret *retributionInfo) error { - // Register for a notification when the breach transaction is confirmed - // on chain. - breachTXID := &ret.commitHash - confChan, err := b.notifier.RegisterConfirmationsNtfn(breachTXID, 1, - uint32(currentHeight)) - if err != nil { - brarLog.Errorf("unable to register for conf updates for txid: "+ - "%v, err: %v", breachTXID, err) - return err + // We load all pending retributions from the database and + // deterministically reconstruct a channel close summary for each. In + // the event that a channel is still open after being breached, we can + // use the close summary to reinitiate a channel close so that the + // breach is reflected in channeldb. + breachRetInfos := make(map[wire.OutPoint]retributionInfo) + closeSummaries := make(map[wire.OutPoint]channeldb.ChannelCloseSummary) + err := b.retributionStore.ForAll(func(ret *retributionInfo) error { + // Extract emitted retribution information. + breachRetInfos[ret.chanPoint] = *ret + + // Deterministically reconstruct channel close summary from + // persisted retribution information and record in breach close + // summaries map under the corresponding channel point. + closeSummary := channeldb.ChannelCloseSummary{ + ChanPoint: ret.chanPoint, + ClosingTXID: ret.commitHash, + RemotePub: &ret.remoteIdentity, + Capacity: ret.capacity, + SettledBalance: ret.settledBalance, + CloseType: channeldb.BreachClose, + IsPending: true, } - - // Launch a new goroutine which to finalize the channel retribution - // after the breach transaction confirms. - b.wg.Add(1) - go b.exactRetribution(confChan, ret) + closeSummaries[ret.chanPoint] = closeSummary return nil }) @@ -147,15 +152,39 @@ func (b *breachArbiter) Start() error { return err } - if len(activeChannels) > 0 { + nActive := len(activeChannels) + if nActive > 0 { brarLog.Infof("Retrieved %v channels from database, watching "+ "with vigilance!", len(activeChannels)) } - // For each of the channels read from disk, we'll create a channel - // state machine in order to watch for any potential channel closures. - channelsToWatch := make([]*lnwallet.LightningChannel, len(activeChannels)) - for i, chanState := range activeChannels { + // Here we will determine a set of channels that will need to be managed + // by the contractObserver. For each of the open channels read from + // disk, we will create a channel state machine that can be used to + // watch for any potential channel closures. We must first exclude any + // channel whose retribution process has been initiated, and proceed to + // mark them as closed. + // The state machines generated for these filtered channels can be + // discarded, as their fate will be placed in the hands of an + // exactRetribution task spawned later. + // + // NOTE Spawning of the exactRetribution task is intentionally postponed + // until after this step in order to ensure that the all breached + // channels are reflected as closed in channeldb and consistent with + // what is checkpointed by the breach arbiter. Instead of treating the + // breached-and-closed and breached-but-still-active channels as + // separate sets of channels, we first + // ensure that all breach-but-still-active channels are promoted to + // breached-and-closed during restart, allowing us to treat them as a + // single set from here on out. This approach also has the added benefit + // of minimizing the likelihood that the wrong number of tasks are + // spawned per breached channel, and prevents us from being in a + // position where + // retribution has completed but the channel is still marked as open in + // channeldb. + channelsToWatch := make([]*lnwallet.LightningChannel, 0, nActive) + for _, chanState := range activeChannels { + // Initialize active channel from persisted channel state. channel, err := lnwallet.NewLightningChannel(nil, b.notifier, b.estimator, chanState) if err != nil { @@ -164,9 +193,73 @@ func (b *breachArbiter) Start() error { return err } - channelsToWatch[i] = channel + // Before marking this as an active channel that the breach + // arbiter should watch, check to see if this channel was + // previously breached. If so, we attempt to reflect this in the + // channeldb by closing the channel. Upon success, we continue + // because the channel is no longer open, and thus does not need + // to be managed by the contractObserver. + chanPoint := chanState.FundingOutpoint + if closeSummary, ok := closeSummaries[chanPoint]; ok { + // Since this channel should not be open, we immediately + // notify the HTLC switch that this link should be + // closed, and that all activity on the link should + // cease. + b.htlcSwitch.CloseLink( + &chanState.FundingOutpoint, + htlcswitch.CloseBreach, + ) + + // Ensure channeldb is consistent with the persisted + // breach. + err := channel.DeleteState(&closeSummary) + if err != nil { + brarLog.Errorf("unable to delete channel "+ + "state: %v", err) + return err + } + + // Now that this channel is both breached _and_ closed, + // we can skip adding it to the `channelsToWatch` since + // we can begin the retribution process immediately. + continue + } + + // Finally, add this channel to breach arbiter's list of + // channels to watch. + channelsToWatch = append(channelsToWatch, channel) + } + // Trim channels in the event that some were filtered. + channelsToWatch = channelsToWatch[:] + + // TODO(roasbeef): instead use closure height of channel + _, currentHeight, err := b.chainIO.GetBestBlock() + if err != nil { + return err } + // Spawn the exactRetribution tasks to monitor and resolve any breaches + // that were loaded from the retribution store. + for chanPoint, closeSummary := range closeSummaries { + // Register for a notification when the breach transaction is + // confirmed on chain. + breachTXID := closeSummary.ClosingTXID + confChan, err := b.notifier.RegisterConfirmationsNtfn( + &breachTXID, 1, uint32(currentHeight)) + if err != nil { + brarLog.Errorf("unable to register for conf updates "+ + "for txid: %v, err: %v", breachTXID, err) + return err + } + + // Launch a new goroutine which to finalize the channel + // retribution after the breach transaction confirms. + retInfo := breachRetInfos[chanPoint] + b.wg.Add(1) + go b.exactRetribution(confChan, &retInfo) + } + + // Start watching the remaining active channels! b.wg.Add(1) go b.contractObserver(channelsToWatch) @@ -191,34 +284,45 @@ func (b *breachArbiter) Start() error { brarLog.Infof("Watching for the closure of ChannelPoint(%v)", pendingClose.ChanPoint) - chanPoint := &pendingClose.ChanPoint - closeTXID := &pendingClose.ClosingTXID + closeTXID := pendingClose.ClosingTXID confNtfn, err := b.notifier.RegisterConfirmationsNtfn( - closeTXID, 1, uint32(currentHeight), + &closeTXID, 1, uint32(currentHeight), ) if err != nil { return err } - go func() { + b.wg.Add(1) + go func(chanPoint wire.OutPoint) { + defer b.wg.Done() + // In the case that the ChainNotifier is shutting down, // all subscriber notification channels will be closed, // generating a nil receive. - confInfo, ok := <-confNtfn.Confirmed - if !ok { - return - } + select { + case confInfo, ok := <-confNtfn.Confirmed: + if !ok { + return + } + + brarLog.Infof("ChannelPoint(%v) is "+ + "fully closed, at height: %v", + chanPoint, confInfo.BlockHeight) - brarLog.Infof("ChannelPoint(%v) is fully closed, "+ - "at height: %v", chanPoint, confInfo.BlockHeight) + // TODO(roasbeef): need to store + // UnilateralCloseSummary on disk so can + // possibly sweep output here - // TODO(roasbeef): need to store UnilateralCloseSummary - // on disk so can possibly sweep output here + err := b.db.MarkChanFullyClosed(&chanPoint) + if err != nil { + brarLog.Errorf("unable to mark chan "+ + "as closed: %v", err) + } - if err := b.db.MarkChanFullyClosed(chanPoint); err != nil { - brarLog.Errorf("unable to mark chan as closed: %v", err) + case <-b.quit: + return } - }() + }(pendingClose.ChanPoint) } return nil @@ -248,7 +352,9 @@ func (b *breachArbiter) Stop() error { // channel into the daemon's wallet. // // NOTE: This MUST be run as a goroutine. -func (b *breachArbiter) contractObserver(activeChannels []*lnwallet.LightningChannel) { +func (b *breachArbiter) contractObserver( + activeChannels []*lnwallet.LightningChannel) { + defer b.wg.Done() // For each active channel found within the database, we launch a @@ -273,7 +379,8 @@ out: case breachInfo := <-b.breachedContracts: _, currentHeight, err := b.chainIO.GetBestBlock() if err != nil { - brarLog.Errorf("unable to get best height: %v", err) + brarLog.Errorf( + "unable to get best height: %v", err) } // A new channel contract has just been breached! We @@ -286,28 +393,26 @@ out: breachTXID, 1, uint32(currentHeight), ) if err != nil { - brarLog.Errorf("unable to register for conf updates for txid: "+ - "%v, err: %v", breachTXID, err) + brarLog.Errorf("unable to register for conf "+ + "updates for txid: %v, err: %v", + breachTXID, err) continue } - brarLog.Warnf("A channel has been breached with txid: %v. "+ - "Waiting for confirmation, then justice will be served!", - breachTXID) - - // Persist the pending retribution state to disk. - if err := b.retributionStore.Add(breachInfo); err != nil { - brarLog.Errorf("unable to persist breach info to db: %v", err) - continue - } + brarLog.Warnf("A channel has been breached with "+ + "txid: %v. Waiting for confirmation, then "+ + "justice will be served!", breachTXID) - // With the notification registered and retribution state persisted, - // we launch a new goroutine which will finalize the channel - // retribution after the breach transaction has been confirmed. + // With the retribution state persisted, channel close + // persisted, and notification registered, we launch a + // new goroutine which will finalize the channel + // retribution after the breach transaction has been + // confirmed. b.wg.Add(1) go b.exactRetribution(confChan, breachInfo) delete(b.breachObservers, breachInfo.chanPoint) + case contract := <-b.newContracts: // A new channel has just been opened within the // daemon, so we launch a new breachObserver to handle @@ -335,9 +440,10 @@ out: b.wg.Add(1) go b.breachObserver(contract, settleSignal) - // TODO(roasbeef): add doneChan to signal to peer continue - // * peer send over to us on loadActiveChanenls, sync - // until we're aware so no state transitions + // TODO(roasbeef): add doneChan to signal to peer + // continue * peer send over to us on + // loadActiveChanenls, sync until we're aware so no + // state transitions case chanPoint := <-b.settledContracts: // A new channel has been closed either unilaterally or // cooperatively, as a result we no longer need a @@ -371,7 +477,8 @@ out: // the lingering funds within the channel into the daemon's wallet. // // NOTE: This MUST be run as a goroutine. -func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, +func (b *breachArbiter) exactRetribution( + confChan *chainntnfs.ConfirmationEvent, breachInfo *retributionInfo) { defer b.wg.Done() @@ -403,9 +510,11 @@ func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, return } - brarLog.Debugf("Broadcasting justice tx: %v", newLogClosure(func() string { - return spew.Sdump(justiceTx) - })) + brarLog.Debugf( + "Broadcasting justice tx: %v", + newLogClosure(func() string { + return spew.Sdump(justiceTx) + })) _, currentHeight, err := b.chainIO.GetBestBlock() if err != nil { @@ -455,16 +564,18 @@ func (b *breachArbiter) exactRetribution(confChan *chainntnfs.ConfirmationEvent, brarLog.Errorf("unable to mark chan as closed: %v", err) } - // Justice has been carried out; we can safely delete the retribution - // info from the database. + // Justice has been carried out; we can safely delete the + // retribution info from the database. err = b.retributionStore.Remove(&breachInfo.chanPoint) if err != nil { - brarLog.Errorf("unable to remove retribution from the db: %v", err) + brarLog.Errorf("unable to remove retribution "+ + "from the db: %v", err) } // TODO(roasbeef): add peer to blacklist? - // TODO(roasbeef): close other active channels with offending peer + // TODO(roasbeef): close other active channels with offending + // peer close(breachInfo.doneChan) @@ -488,7 +599,8 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, chanPoint := contract.ChannelPoint() - brarLog.Debugf("Breach observer for ChannelPoint(%v) started", chanPoint) + brarLog.Debugf( + "Breach observer for ChannelPoint(%v) started", chanPoint) select { // A read from this channel indicates that the contract has been @@ -502,18 +614,32 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, case closeInfo := <-contract.UnilateralClose: // Launch a goroutine to cancel out this contract within the // breachArbiter's main goroutine. + b.wg.Add(1) go func() { - b.settledContracts <- chanPoint + defer b.wg.Done() + + select { + case b.settledContracts <- chanPoint: + case <-b.quit: + } }() // Next, we'll launch a goroutine to wait until the closing // transaction has been confirmed so we can mark the contract - // as resolved in the database. + // as resolved in the database. This go routine is _not_ + // tracked by the breach aribter's wait group since the callback + // may not be executed before shutdown, potentially leading to + // a deadlock. // // TODO(roasbeef): also notify utxoNursery, might've had // outbound HTLC's in flight - go waitForChanToClose(uint32(closeInfo.SpendingHeight), b.notifier, - nil, chanPoint, closeInfo.SpenderTxHash, func() { + go waitForChanToClose( + uint32(closeInfo.SpendingHeight), + b.notifier, + nil, + chanPoint, + closeInfo.SpenderTxHash, + func() { // As we just detected a channel was closed via // a unilateral commitment broadcast by the // remote party, we'll need to sweep our main @@ -528,11 +654,14 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, ) if err != nil { brarLog.Errorf("unable to "+ - "generate sweep tx: %v", err) + "generate sweep tx: %v", + err) goto close } - err = b.wallet.PublishTransaction(sweepTx) + err = b.wallet.PublishTransaction( + sweepTx, + ) if err != nil { brarLog.Errorf("unable to "+ "broadcast tx: %v", err) @@ -540,11 +669,14 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, } close: - brarLog.Infof("Force closed ChannelPoint(%v) is "+ - "fully closed, updating DB", chanPoint) + brarLog.Infof("Force closed ChannelPoint(%v) "+ + "is fully closed, updating DB", + chanPoint) - if err := b.db.MarkChanFullyClosed(chanPoint); err != nil { - brarLog.Errorf("unable to mark chan as closed: %v", err) + err := b.db.MarkChanFullyClosed(chanPoint) + if err != nil { + brarLog.Errorf("unable to mark chan "+ + "as closed: %v", err) } }) @@ -563,21 +695,10 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, // links associated with this peer. b.htlcSwitch.CloseLink(chanPoint, htlcswitch.CloseBreach) chanInfo := contract.StateSnapshot() - closeInfo := &channeldb.ChannelCloseSummary{ - ChanPoint: *chanPoint, - ClosingTXID: breachInfo.BreachTransaction.TxHash(), - RemotePub: &chanInfo.RemoteIdentity, - Capacity: chanInfo.Capacity, - SettledBalance: chanInfo.LocalBalance.ToSatoshis(), - CloseType: channeldb.BreachClose, - IsPending: true, - } - if err := contract.DeleteState(closeInfo); err != nil { - brarLog.Errorf("unable to delete channel state: %v", err) - } // TODO(roasbeef): need to handle case of remote broadcast - // mid-local initiated state-transition, possible false-positive? + // mid-local initiated state-transition, possible + // false-positive? // First we generate the witness generation function which will // be used to sweep the output only we can satisfy on the @@ -591,7 +712,8 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, desc.SigHashes = hc desc.InputIndex = inputIndex - return lnwallet.CommitSpendNoDelay(b.wallet.Cfg.Signer, &desc, tx) + return lnwallet.CommitSpendNoDelay( + b.wallet.Cfg.Signer, &desc, tx) } // Next we create the witness generation function that will be @@ -606,32 +728,67 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, desc.SigHashes = hc desc.InputIndex = inputIndex - return lnwallet.CommitSpendRevoke(b.wallet.Cfg.Signer, &desc, tx) + return lnwallet.CommitSpendRevoke( + b.wallet.Cfg.Signer, &desc, tx) } - // Finally, we send the retribution information into the breachArbiter - // event loop to deal swift justice. + // Assemble the retribution information that parameterizes the + // construction of transactions required to correct the breach. // TODO(roasbeef): populate htlc breaches - b.breachedContracts <- &retributionInfo{ + retInfo := &retributionInfo{ commitHash: breachInfo.BreachTransaction.TxHash(), chanPoint: *chanPoint, + remoteIdentity: chanInfo.RemoteIdentity, + capacity: chanInfo.Capacity, + settledBalance: chanInfo.LocalBalance.ToSatoshis(), + selfOutput: &breachedOutput{ amt: btcutil.Amount(localSignDesc.Output.Value), outpoint: breachInfo.LocalOutpoint, signDescriptor: localSignDesc, - witnessType: localWitnessType, + witnessType: lnwallet.CommitmentNoDelay, + witnessFunc: localWitness, }, revokedOutput: &breachedOutput{ amt: btcutil.Amount(remoteSignDesc.Output.Value), outpoint: breachInfo.RemoteOutpoint, signDescriptor: remoteSignDesc, - witnessType: remoteWitnessType, + witnessType: lnwallet.CommitmentRevoke, + witnessFunc: remoteWitness, }, htlcOutputs: []*breachedOutput{}, - doneChan: make(chan struct{}), + + doneChan: make(chan struct{}), + } + + // Persist the pending retribution state to disk. + if err := b.retributionStore.Add(retInfo); err != nil { + brarLog.Errorf("unable to persist "+ + "retribution info to db: %v", err) + } + + closeInfo := &channeldb.ChannelCloseSummary{ + ChanPoint: *chanPoint, + ClosingTXID: breachInfo.BreachTransaction.TxHash(), + RemotePub: &chanInfo.RemoteIdentity, + Capacity: chanInfo.Capacity, + SettledBalance: chanInfo.LocalBalance.ToSatoshis(), + CloseType: channeldb.BreachClose, + IsPending: true, + } + if err := contract.DeleteState(closeInfo); err != nil { + brarLog.Errorf( + "unable to delete channel state: %v", err) + } + + // Finally, we send the retribution information into the + // breachArbiter event loop to deal swift justice. + select { + case b.breachedContracts <- retInfo: + case <-b.quit: } case <-b.quit: @@ -646,8 +803,9 @@ type breachedOutput struct { amt btcutil.Amount outpoint wire.OutPoint - signDescriptor *lnwallet.SignDescriptor + signDescriptor lnwallet.SignDescriptor witnessType lnwallet.WitnessType + witnessFunc lnwallet.WitnessGenerator twoStageClaim bool } @@ -661,6 +819,14 @@ type retributionInfo struct { commitHash chainhash.Hash chanPoint wire.OutPoint + // Fields copied from channel snapshot when a breach is detected. This + // is necessary for deterministically constructing the channel close + // summary in the event that the breach arbiter crashes before closing + // the channel. + remoteIdentity btcec.PublicKey + capacity btcutil.Amount + settledBalance btcutil.Amount + selfOutput *breachedOutput revokedOutput *breachedOutput @@ -674,7 +840,9 @@ type retributionInfo struct { // the funds within the channel which we are now entitled to due to a breach of // the channel's contract by the counterparty. This function returns a *fully* // signed transaction with the witness for each input fully in place. -func (b *breachArbiter) createJusticeTx(r *retributionInfo) (*wire.MsgTx, error) { +func (b *breachArbiter) createJusticeTx( + r *retributionInfo) (*wire.MsgTx, error) { + // First, we obtain a new public key script from the wallet which we'll // sweep the funds to. // TODO(roasbeef): possibly create many outputs to minimize change in @@ -684,8 +852,19 @@ func (b *breachArbiter) createJusticeTx(r *retributionInfo) (*wire.MsgTx, error) return nil, err } - // Before creating the actual TxOut, we'll need to calculate the proper fee - // to attach to the transaction to ensure a timely confirmation. + r.selfOutput.witnessFunc = r.selfOutput.witnessType.GenWitnessFunc( + &b.wallet.Cfg.Signer, &r.selfOutput.signDescriptor) + + r.revokedOutput.witnessFunc = r.revokedOutput.witnessType.GenWitnessFunc( + &b.wallet.Cfg.Signer, &r.revokedOutput.signDescriptor) + + for i := range r.htlcOutputs { + r.htlcOutputs[i].witnessFunc = r.htlcOutputs[i].witnessType.GenWitnessFunc( + &b.wallet.Cfg.Signer, &r.htlcOutputs[i].signDescriptor) + } + + // Before creating the actual TxOut, we'll need to calculate the proper + // fee to attach to the transaction to ensure a timely confirmation. // TODO(roasbeef): remove hard-coded fee totalAmt := r.selfOutput.amt + r.revokedOutput.amt sweepedAmt := int64(totalAmt - 5000) @@ -711,17 +890,13 @@ func (b *breachArbiter) createJusticeTx(r *retributionInfo) (*wire.MsgTx, error) // witnesses for both commitment outputs, and all the pending HTLCs at // this state in the channel's history. // TODO(roasbeef): handle the 2-layer HTLCs - localWitnessFunc := r.selfOutput.witnessType.GenWitnessFunc( - &b.wallet.Signer, r.selfOutput.signDescriptor) - localWitness, err := localWitnessFunc(justiceTx, hashCache, 0) + localWitness, err := r.selfOutput.witnessFunc(justiceTx, hashCache, 0) if err != nil { return nil, err } justiceTx.TxIn[0].Witness = localWitness - remoteWitnessFunc := r.revokedOutput.witnessType.GenWitnessFunc( - &b.wallet.Signer, r.revokedOutput.signDescriptor) - remoteWitness, err := remoteWitnessFunc(justiceTx, hashCache, 1) + remoteWitness, err := r.revokedOutput.witnessFunc(justiceTx, hashCache, 1) if err != nil { return nil, err } @@ -738,7 +913,9 @@ func (b *breachArbiter) createJusticeTx(r *retributionInfo) (*wire.MsgTx, error) // TODO(roasbeef): alternative options // * leave the output in the chain, use as input to future funding tx // * leave output in the chain, extend wallet to add knowledge of how to claim -func (b *breachArbiter) craftCommitSweepTx(closeInfo *lnwallet.UnilateralCloseSummary) (*wire.MsgTx, error) { +func (b *breachArbiter) craftCommitSweepTx( + closeInfo *lnwallet.UnilateralCloseSummary) (*wire.MsgTx, error) { + // First, we'll fetch a fresh script that we can use to sweep the funds // under the control of the wallet. sweepPkScript, err := newSweepPkScript(b.wallet) @@ -796,33 +973,32 @@ func (b *breachArbiter) craftCommitSweepTx(closeInfo *lnwallet.UnilateralCloseSu return sweepTx, nil } -// breachedOutput contains all the information needed to sweep a breached -// output. A breached output is an output that we are now entitled to due to a -// revoked commitment transaction being broadcast. -type breachedOutput struct { - amt btcutil.Amount - outpoint wire.OutPoint - - signDescriptor *lnwallet.SignDescriptor - witnessType lnwallet.WitnessType - - twoStageClaim bool -} - -// retribution encapsulates all the data needed to sweep all the contested -// funds within a channel whose contract has been breached by the prior -// counterparty. This struct is used to create the justice transaction which -// spends all outputs of the commitment transaction into an output controlled -// by the wallet. -type retributionInfo struct { - commitHash chainhash.Hash - chanPoint wire.OutPoint - - selfOutput *breachedOutput - revokedOutput *breachedOutput - htlcOutputs []*breachedOutput - - doneChan chan struct{} +// RetributionStore provides an interface for managing a persistent map from +// wire.OutPoint -> retributionInfo. Upon learning of a breach, a BreachArbiter +// should record the retributionInfo for the breached channel, which serves a +// checkpoint in the event that retribution needs to be resumed after failure. +// A RetributionStore provides an interface for managing the persisted set, as +// well as mapping user defined functions over the entire on-disk contents. +// +// Calls to RetributionStore may occur concurrently. A concrete instance of +// RetributionStore should use appropriate synchronization primitives, or +// be otherwise safe for concurrent access. +type RetributionStore interface { + + // Add persists the retributionInfo to disk, using the information's + // chanPoint as the key. This method should overwrite any existing + // entires found under the same key, and an error should be raised if + // the addition fails. + Add(retInfo *retributionInfo) error + + // Remove deletes the retributionInfo from disk, if any exists, under + // the given key. An error should be re raised if the removal fails. + Remove(key *wire.OutPoint) error + + // ForAll iterates over the existing on-disk contents and applies a + // chosen, read-only callback to each. This method should ensure that it + // immediately propagate any errors generated by the callback. + ForAll(cb func(*retributionInfo) error) error } // retributionStore handles persistence of retribution states to disk and is @@ -844,8 +1020,8 @@ func newRetributionStore(db *channeldb.DB) *retributionStore { // to disk. func (rs *retributionStore) Add(ret *retributionInfo) error { return rs.db.Update(func(tx *bolt.Tx) error { - // If this is our first contract breach, the retributionBucket won't - // exist, in which case, we just create a new bucket. + // If this is our first contract breach, the retributionBucket + // won't exist, in which case, we just create a new bucket. retBucket, err := tx.CreateBucketIfNotExists(retributionBucket) if err != nil { return err @@ -861,7 +1037,10 @@ func (rs *retributionStore) Add(ret *retributionInfo) error { return err } - if err := retBucket.Put(outBuf.Bytes(), retBuf.Bytes()); err != nil { + if err := retBucket.Put( + outBuf.Bytes(), + retBuf.Bytes(), + ); err != nil { return err } @@ -874,12 +1053,13 @@ func (rs *retributionStore) Remove(key *wire.OutPoint) error { return rs.db.Update(func(tx *bolt.Tx) error { retBucket := tx.Bucket(retributionBucket) - // We return an error if the bucket is not already created, since normal - // operation of the breach arbiter should never try to remove a - // finalized retribution state that is not already stored in the db. + // We return an error if the bucket is not already created, + // since normal operation of the breach arbiter should never try + // to remove a finalized retribution state that is not already + // stored in the db. if retBucket == nil { - return errors.New("unable to remove retribution because the " + - "db bucket doesn't exist.") + return errors.New("unable to remove retribution " + + "because the db bucket doesn't exist.") } var outBuf bytes.Buffer @@ -899,17 +1079,21 @@ func (rs *retributionStore) Remove(key *wire.OutPoint) error { // callback function on each retribution. func (rs *retributionStore) ForAll(cb func(*retributionInfo) error) error { return rs.db.View(func(tx *bolt.Tx) error { - // If the bucket does not exist, then there are no pending retributions. + // If the bucket does not exist, then there are no pending + // retributions. retBucket := tx.Bucket(retributionBucket) if retBucket == nil { return nil } - // Otherwise, we fetch each serialized retribution info, deserialize - // it, and execute the passed in callback function on it. + // Otherwise, we fetch each serialized retribution info, + // deserialize it, and execute the passed in callback function + // on it. return retBucket.ForEach(func(outBytes, retBytes []byte) error { ret := &retributionInfo{} - if err := ret.Decode(bytes.NewBuffer(retBytes)); err != nil { + if err := ret.Decode( + bytes.NewBuffer(retBytes), + ); err != nil { return err } @@ -920,6 +1104,8 @@ func (rs *retributionStore) ForAll(cb func(*retributionInfo) error) error { // Encode serializes the retribution into the passed byte stream. func (ret *retributionInfo) Encode(w io.Writer) error { + var scratch [8]byte + if _, err := w.Write(ret.commitHash[:]); err != nil { return err } @@ -928,6 +1114,21 @@ func (ret *retributionInfo) Encode(w io.Writer) error { return err } + if _, err := w.Write( + ret.remoteIdentity.SerializeCompressed()); err != nil { + return err + } + + binary.BigEndian.PutUint64(scratch[:8], uint64(ret.capacity)) + if _, err := w.Write(scratch[:8]); err != nil { + return err + } + + binary.BigEndian.PutUint64(scratch[:8], uint64(ret.settledBalance)) + if _, err := w.Write(scratch[:8]); err != nil { + return err + } + if err := ret.selfOutput.Encode(w); err != nil { return err } @@ -952,12 +1153,12 @@ func (ret *retributionInfo) Encode(w io.Writer) error { // Dencode deserializes a retribution from the passed byte stream. func (ret *retributionInfo) Decode(r io.Reader) error { - var scratch [32]byte + var scratch [33]byte - if _, err := io.ReadFull(r, scratch[:]); err != nil { + if _, err := io.ReadFull(r, scratch[:32]); err != nil { return err } - hash, err := chainhash.NewHash(scratch[:]) + hash, err := chainhash.NewHash(scratch[:32]) if err != nil { return err } @@ -967,6 +1168,26 @@ func (ret *retributionInfo) Decode(r io.Reader) error { return err } + if _, err = io.ReadFull(r, scratch[:33]); err != nil { + return err + } + remoteIdentity, err := btcec.ParsePubKey(scratch[:33], btcec.S256()) + if err != nil { + return err + } + ret.remoteIdentity = *remoteIdentity + + if _, err := io.ReadFull(r, scratch[:8]); err != nil { + return err + } + ret.capacity = btcutil.Amount(binary.BigEndian.Uint64(scratch[:8])) + + if _, err := io.ReadFull(r, scratch[:8]); err != nil { + return err + } + ret.settledBalance = btcutil.Amount( + binary.BigEndian.Uint64(scratch[:8])) + ret.selfOutput = &breachedOutput{} if err := ret.selfOutput.Decode(r); err != nil { return err @@ -1007,7 +1228,8 @@ func (bo *breachedOutput) Encode(w io.Writer) error { return err } - if err := lnwallet.WriteSignDescriptor(w, bo.signDescriptor); err != nil { + if err := lnwallet.WriteSignDescriptor( + w, &bo.signDescriptor); err != nil { return err } @@ -1041,16 +1263,16 @@ func (bo *breachedOutput) Decode(r io.Reader) error { return err } - signDescriptor := lnwallet.SignDescriptor{} - if err := lnwallet.ReadSignDescriptor(r, &signDescriptor); err != nil { + if err := lnwallet.ReadSignDescriptor( + r, &bo.signDescriptor); err != nil { return err } - bo.signDescriptor = &signDescriptor if _, err := io.ReadFull(r, scratch[:2]); err != nil { return err } - bo.witnessType = lnwallet.WitnessType(binary.BigEndian.Uint16(scratch[:2])) + bo.witnessType = lnwallet.WitnessType( + binary.BigEndian.Uint16(scratch[:2])) if _, err := io.ReadFull(r, scratch[:1]); err != nil { return err diff --git a/breacharbiter_test.go b/breacharbiter_test.go index d2732d8faf28..74722beba3b9 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "reflect" + "sync" "testing" "github.com/lightningnetwork/lnd/channeldb" @@ -77,7 +78,7 @@ var ( breachSignDescs = []lnwallet.SignDescriptor{ { - PrivateTweak: []byte{ + SingleTweak: []byte{ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, @@ -107,7 +108,7 @@ var ( HashType: txscript.SigHashAll, }, { - PrivateTweak: []byte{ + SingleTweak: []byte{ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, @@ -137,7 +138,7 @@ var ( HashType: txscript.SigHashAll, }, { - PrivateTweak: []byte{ + SingleTweak: []byte{ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, @@ -199,10 +200,12 @@ var ( 0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9, 0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53, }, - chanPoint: breachOutPoints[0], - selfOutput: &breachedOutputs[0], - revokedOutput: &breachedOutputs[1], - htlcOutputs: []*breachedOutput{}, + chanPoint: breachOutPoints[0], + capacity: btcutil.Amount(1e7), + settledBalance: btcutil.Amount(1e7), + selfOutput: &breachedOutputs[0], + revokedOutput: &breachedOutputs[1], + htlcOutputs: []*breachedOutput{}, }, { commitHash: [chainhash.HashSize]byte{ @@ -211,9 +214,11 @@ var ( 0x2d, 0xe7, 0x93, 0xe4, 0xb7, 0x25, 0xb8, 0x4d, 0x1f, 0xb, 0x4c, 0xf9, 0x9e, 0xc5, 0x8c, 0xe9, }, - chanPoint: breachOutPoints[1], - selfOutput: &breachedOutputs[0], - revokedOutput: &breachedOutputs[1], + chanPoint: breachOutPoints[1], + capacity: btcutil.Amount(1e7), + settledBalance: btcutil.Amount(1e7), + selfOutput: &breachedOutputs[0], + revokedOutput: &breachedOutputs[1], htlcOutputs: []*breachedOutput{ &breachedOutputs[1], &breachedOutputs[2], @@ -224,7 +229,7 @@ var ( // Parse the pubkeys in the breached outputs. func initBreachedOutputs() error { - for i := 0; i < len(breachedOutputs); i++ { + for i := range breachedOutputs { bo := &breachedOutputs[i] // Parse the sign descriptor's pubkey. @@ -234,7 +239,7 @@ func initBreachedOutputs() error { return fmt.Errorf("unable to parse pubkey: %v", breachKeys[i]) } sd.PubKey = pubkey - bo.signDescriptor = sd + bo.signDescriptor = *sd } return nil @@ -278,6 +283,12 @@ func TestRetributionSerialization(t *testing.T) { for i := 0; i < len(retributions); i++ { ret := &retributions[i] + remoteIdentity, err := btcec.ParsePubKey(breachKeys[i], btcec.S256()) + if err != nil { + t.Fatalf("unable to parse public key [%v]: %v", i, err) + } + ret.remoteIdentity = *remoteIdentity + var buf bytes.Buffer if err := ret.Encode(&buf); err != nil { @@ -298,37 +309,104 @@ func TestRetributionSerialization(t *testing.T) { } } -// TODO(phlip9): reuse existing function? -// makeTestDB creates a new instance of the ChannelDB for testing purposes. A -// callback which cleans up the created temporary directories is also returned -// and intended to be executed after the test completes. -func makeTestDB() (*channeldb.DB, func(), error) { - var db *channeldb.DB +// copyRetInfo creates a complete copy of the given retributionInfo. +func copyRetInfo(retInfo *retributionInfo) *retributionInfo { + ret := &retributionInfo{ + commitHash: retInfo.commitHash, + chanPoint: retInfo.chanPoint, + remoteIdentity: retInfo.remoteIdentity, + capacity: retInfo.capacity, + settledBalance: retInfo.settledBalance, + selfOutput: retInfo.selfOutput, + revokedOutput: retInfo.revokedOutput, + htlcOutputs: make([]*breachedOutput, len(retInfo.htlcOutputs)), + doneChan: make(chan struct{}), + } + + for i, htlco := range retInfo.htlcOutputs { + ret.htlcOutputs[i] = htlco + } + + return ret +} + +// mockRetributionStore implements the RetributionStore interface and is backed +// by an in-memory map. Access to the internal state is provided by a mutex. +// TODO(cfromknecht) extend to support and test controlled failures. +type mockRetributionStore struct { + mu sync.Mutex + state map[wire.OutPoint]*retributionInfo +} + +func newMockRetributionStore() *mockRetributionStore { + return &mockRetributionStore{ + mu: sync.Mutex{}, + state: make(map[wire.OutPoint]*retributionInfo), + } +} + +func (rs *mockRetributionStore) Add(retInfo *retributionInfo) error { + rs.mu.Lock() + rs.state[retInfo.chanPoint] = copyRetInfo(retInfo) + rs.mu.Unlock() + + return nil +} + +func (rs *mockRetributionStore) Remove(key *wire.OutPoint) error { + rs.mu.Lock() + delete(rs.state, *key) + rs.mu.Unlock() + + return nil +} + +func (rs *mockRetributionStore) ForAll(cb func(*retributionInfo) error) error { + rs.mu.Lock() + defer rs.mu.Unlock() + for _, retInfo := range rs.state { + if err := cb(copyRetInfo(retInfo)); err != nil { + return err + } + } + + return nil +} + +// TestMockRetributionStore instantiates a mockRetributionStore and tests its +// behavior using the general RetributionStore test suite. +func TestMockRetributionStore(t *testing.T) { + mrs := newMockRetributionStore() + testRetributionStore(mrs, t) +} + +// TestChannelDBRetributionStore instantiates a retributionStore backed by a +// channeldb.DB, and tests its behavior using the general RetributionStore test +// suite. +func TestChannelDBRetributionStore(t *testing.T) { // First, create a temporary directory to be used for the duration of // this test. tempDirName, err := ioutil.TempDir("", "channeldb") if err != nil { - return nil, nil, err + t.Fatalf("unable to initialize temp directory for channeldb: %v", err) } + defer os.RemoveAll(tempDirName) // Next, create channeldb for the first time. - db, err = channeldb.Open(tempDirName) + db, err := channeldb.Open(tempDirName) if err != nil { - return nil, nil, err - } - - cleanUp := func() { - if db != nil { - db.Close() - } - os.RemoveAll(tempDirName) + t.Fatalf("unable to open channeldb: %v", err) } + defer db.Close() - return db, cleanUp, nil + // Finally, instantiate retribution store and execute RetributionStore test + // suite. + rs := newRetributionStore(db) + testRetributionStore(rs, t) } -func countRetributions(t *testing.T, rs *retributionStore) int { +func countRetributions(t *testing.T, rs RetributionStore) int { count := 0 err := rs.ForAll(func(_ *retributionInfo) error { count++ @@ -341,32 +419,29 @@ func countRetributions(t *testing.T, rs *retributionStore) int { } // Test that the retribution persistence layer works. -func TestRetributionStore(t *testing.T) { - db, cleanUp, err := makeTestDB() - defer cleanUp() - if err != nil { - t.Fatalf("unable to create test db: %v", err) - } - +func testRetributionStore(rs RetributionStore, t *testing.T) { if err := initBreachedOutputs(); err != nil { t.Fatalf("unable to init breached outputs: %v", err) } - rs := newRetributionStore(db) - // Make sure that a new retribution store is actually emtpy. if count := countRetributions(t, rs); count != 0 { t.Fatalf("expected 0 retributions, found %v", count) } - // Add some retribution states to the store. + // Add first retribution state to the store. if err := rs.Add(&retributions[0]); err != nil { t.Fatalf("unable to add to retribution store: %v", err) } + // Ensure that the retribution store has one retribution. + if count := countRetributions(t, rs); count != 1 { + t.Fatalf("expected 1 retributions, found %v", count) + } + + // Add second retribution state to the store. if err := rs.Add(&retributions[1]); err != nil { t.Fatalf("unable to add to retribution store: %v", err) } - // There should be 2 retributions in the store. if count := countRetributions(t, rs); count != 2 { t.Fatalf("expected 2 retributions, found %v", count) @@ -387,6 +462,11 @@ func TestRetributionStore(t *testing.T) { if err := rs.Remove(&retributions[0].chanPoint); err != nil { t.Fatalf("unable to remove from retribution store: %v", err) } + // Ensure that the retribution store has one retribution. + if count := countRetributions(t, rs); count != 1 { + t.Fatalf("expected 1 retributions, found %v", count) + } + if err := rs.Remove(&retributions[1].chanPoint); err != nil { t.Fatalf("unable to remove from retribution store: %v", err) } From e24df2e304cce892f51780826449ffa5c212430e Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Thu, 27 Jul 2017 17:43:38 -0700 Subject: [PATCH 10/15] breacharbiter_test: adds RetributionStore persistence unit tests --- breacharbiter_test.go | 444 +++++++++++++++++++++++++++++++++++------- 1 file changed, 374 insertions(+), 70 deletions(-) diff --git a/breacharbiter_test.go b/breacharbiter_test.go index 74722beba3b9..e8c6afbb5da5 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -2,7 +2,6 @@ package main import ( "bytes" - "errors" "fmt" "io/ioutil" "os" @@ -10,6 +9,7 @@ import ( "sync" "testing" + "github.com/btcsuite/btclog" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwallet" "github.com/roasbeef/btcd/btcec" @@ -192,7 +192,8 @@ var ( }, } - retributions = []retributionInfo{ + retributionMap = make(map[wire.OutPoint]retributionInfo) + retributions = []retributionInfo{ { commitHash: [chainhash.HashSize]byte{ 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, @@ -227,6 +228,83 @@ var ( } ) +func init() { + // Ensure that breached outputs are initialized before starting tests. + if err := initBreachedOutputs(); err != nil { + panic(err) + } + + // Populate a retribution map to for convenience, to allow lookups by + // channel point. + for i := range retributions { + retInfo := &retributions[i] + retInfo.remoteIdentity = *breachedOutputs[i].signDescriptor.PubKey + retributionMap[retInfo.chanPoint] = *retInfo + } +} + +// FailingRetributionStore wraps a RetributionStore and supports controlled +// restarts of the persistent instance. This allows us to test (1) that no +// modifications to the entries are made between calls or through side effects, +// and (2) that the database is actually being persisted between actions. +type FailingRetributionStore interface { + RetributionStore + + Restart() +} + +// failingRetributionStore is a concrete implementation of a +// FailingRetributionStore. It wraps an underlying RetributionStore and is +// parameterized entirely by a restart function, which is intended to simulate a +// full stop/start of the store. +type failingRetributionStore struct { + mu sync.Mutex + + rs RetributionStore + + restart func() RetributionStore +} + +// newFailingRetributionStore creates a new failing retribution store. The given +// restart closure should ensure that it is reloading its contents from the +// persistent source. +func newFailingRetributionStore( + restart func() RetributionStore) *failingRetributionStore { + + return &failingRetributionStore{ + mu: sync.Mutex{}, + rs: restart(), + restart: restart, + } +} + +func (frs *failingRetributionStore) Restart() { + frs.mu.Lock() + frs.rs = frs.restart() + frs.mu.Unlock() +} + +func (frs *failingRetributionStore) Add(retInfo *retributionInfo) error { + frs.mu.Lock() + defer frs.mu.Unlock() + + return frs.rs.Add(retInfo) +} + +func (frs *failingRetributionStore) Remove(key *wire.OutPoint) error { + frs.mu.Lock() + defer frs.mu.Unlock() + + return frs.rs.Remove(key) +} + +func (frs *failingRetributionStore) ForAll(cb func(*retributionInfo) error) error { + frs.mu.Lock() + defer frs.mu.Unlock() + + return frs.rs.ForAll(cb) +} + // Parse the pubkeys in the breached outputs. func initBreachedOutputs() error { for i := range breachedOutputs { @@ -236,7 +314,8 @@ func initBreachedOutputs() error { sd := &breachSignDescs[i] pubkey, err := btcec.ParsePubKey(breachKeys[i], btcec.S256()) if err != nil { - return fmt.Errorf("unable to parse pubkey: %v", breachKeys[i]) + return fmt.Errorf("unable to parse pubkey: %v", + breachKeys[i]) } sd.PubKey = pubkey bo.signDescriptor = *sd @@ -247,26 +326,25 @@ func initBreachedOutputs() error { // Test that breachedOutput Encode/Decode works. func TestBreachedOutputSerialization(t *testing.T) { - if err := initBreachedOutputs(); err != nil { - t.Fatalf("unable to init breached outputs: %v", err) - } - for i := 0; i < len(breachedOutputs); i++ { bo := &breachedOutputs[i] var buf bytes.Buffer if err := bo.Encode(&buf); err != nil { - t.Fatalf("unable to serialize breached output [%v]: %v", i, err) + t.Fatalf("unable to serialize breached output [%v]: %v", + i, err) } desBo := &breachedOutput{} if err := desBo.Decode(&buf); err != nil { - t.Fatalf("unable to deserialize breached output [%v]: %v", i, err) + t.Fatalf("unable to deserialize "+ + "breached output [%v]: %v", i, err) } if !reflect.DeepEqual(bo, desBo) { - t.Fatalf("original and deserialized breached outputs not equal:\n"+ + t.Fatalf("original and deserialized "+ + "breached outputs not equal:\n"+ "original : %+v\n"+ "deserialized : %+v\n", bo, desBo) @@ -276,32 +354,25 @@ func TestBreachedOutputSerialization(t *testing.T) { // Test that retribution Encode/Decode works. func TestRetributionSerialization(t *testing.T) { - if err := initBreachedOutputs(); err != nil { - t.Fatalf("unable to init breached outputs: %v", err) - } - for i := 0; i < len(retributions); i++ { ret := &retributions[i] - remoteIdentity, err := btcec.ParsePubKey(breachKeys[i], btcec.S256()) - if err != nil { - t.Fatalf("unable to parse public key [%v]: %v", i, err) - } - ret.remoteIdentity = *remoteIdentity - var buf bytes.Buffer if err := ret.Encode(&buf); err != nil { - t.Fatalf("unable to serialize retribution [%v]: %v", i, err) + t.Fatalf("unable to serialize retribution [%v]: %v", + i, err) } desRet := &retributionInfo{} if err := desRet.Decode(&buf); err != nil { - t.Fatalf("unable to deserialize retribution [%v]: %v", i, err) + t.Fatalf("unable to deserialize retribution [%v]: %v", + i, err) } if !reflect.DeepEqual(ret, desRet) { - t.Fatalf("original and deserialized retribution infos not equal:\n"+ + t.Fatalf("original and deserialized "+ + "retribution infos not equal:\n"+ "original : %+v\n"+ "deserialized : %+v\n", ret, desRet) @@ -311,6 +382,8 @@ func TestRetributionSerialization(t *testing.T) { // copyRetInfo creates a complete copy of the given retributionInfo. func copyRetInfo(retInfo *retributionInfo) *retributionInfo { + nHtlcs := len(retInfo.htlcOutputs) + ret := &retributionInfo{ commitHash: retInfo.commitHash, chanPoint: retInfo.chanPoint, @@ -319,8 +392,8 @@ func copyRetInfo(retInfo *retributionInfo) *retributionInfo { settledBalance: retInfo.settledBalance, selfOutput: retInfo.selfOutput, revokedOutput: retInfo.revokedOutput, - htlcOutputs: make([]*breachedOutput, len(retInfo.htlcOutputs)), - doneChan: make(chan struct{}), + htlcOutputs: make([]*breachedOutput, nHtlcs), + doneChan: retInfo.doneChan, } for i, htlco := range retInfo.htlcOutputs { @@ -374,11 +447,47 @@ func (rs *mockRetributionStore) ForAll(cb func(*retributionInfo) error) error { return nil } +var retributionStoreTestSuite = []struct { + name string + test func(FailingRetributionStore, *testing.T) +}{ + { + "Initialization", + testRetributionStoreInit, + }, + { + "Add/Remove", + testRetributionStoreAddRemove, + }, + { + "Persistence", + testRetributionStorePersistence, + }, + { + "Overwrite", + testRetributionStoreOverwrite, + }, + { + "RemoveEmpty", + testRetributionStoreRemoveEmpty, + }, +} + // TestMockRetributionStore instantiates a mockRetributionStore and tests its // behavior using the general RetributionStore test suite. func TestMockRetributionStore(t *testing.T) { - mrs := newMockRetributionStore() - testRetributionStore(mrs, t) + for _, test := range retributionStoreTestSuite { + t.Run( + "mockRetributionStore."+test.name, + func(tt *testing.T) { + mrs := newMockRetributionStore() + frs := newFailingRetributionStore( + func() RetributionStore { return mrs }, + ) + test.test(frs, tt) + }, + ) + } } // TestChannelDBRetributionStore instantiates a retributionStore backed by a @@ -389,10 +498,14 @@ func TestChannelDBRetributionStore(t *testing.T) { // this test. tempDirName, err := ioutil.TempDir("", "channeldb") if err != nil { - t.Fatalf("unable to initialize temp directory for channeldb: %v", err) + t.Fatalf("unable to initialize temp "+ + "directory for channeldb: %v", err) } defer os.RemoveAll(tempDirName) + // Disable logging to prevent panics bc. of global state + channeldb.UseLogger(btclog.Disabled) + // Next, create channeldb for the first time. db, err := channeldb.Open(tempDirName) if err != nil { @@ -400,12 +513,40 @@ func TestChannelDBRetributionStore(t *testing.T) { } defer db.Close() - // Finally, instantiate retribution store and execute RetributionStore test - // suite. - rs := newRetributionStore(db) - testRetributionStore(rs, t) + restartDb := func() RetributionStore { + // Close and reopen channeldb + if err = db.Close(); err != nil { + t.Fatalf("unalbe to close channeldb during restart: %v", + err) + } + db, err = channeldb.Open(tempDirName) + if err != nil { + t.Fatalf("unable to open channeldb: %v", err) + } + + return newRetributionStore(db) + } + + // Finally, instantiate retribution store and execute RetributionStore + // test suite. + for _, test := range retributionStoreTestSuite { + t.Run( + "channeldbDBRetributionStore."+test.name, + func(tt *testing.T) { + if err = db.Wipe(); err != nil { + t.Fatalf("unable to wipe channeldb: %v", + err) + } + + frs := newFailingRetributionStore(restartDb) + test.test(frs, tt) + }, + ) + } } +// countRetributions uses a retribution store's ForAll to count the number of +// elements emitted from the store. func countRetributions(t *testing.T, rs RetributionStore) int { count := 0 err := rs.ForAll(func(_ *retributionInfo) error { @@ -418,61 +559,224 @@ func countRetributions(t *testing.T, rs RetributionStore) int { return count } -// Test that the retribution persistence layer works. -func testRetributionStore(rs RetributionStore, t *testing.T) { - if err := initBreachedOutputs(); err != nil { - t.Fatalf("unable to init breached outputs: %v", err) - } - +// testRetributionStore executes a generic test suite for any concrete +// implementation of the RetributionStore interface. +func testRetributionStoreAddRemove(frs FailingRetributionStore, t *testing.T) { // Make sure that a new retribution store is actually emtpy. - if count := countRetributions(t, rs); count != 0 { + if count := countRetributions(t, frs); count != 0 { t.Fatalf("expected 0 retributions, found %v", count) } - // Add first retribution state to the store. - if err := rs.Add(&retributions[0]); err != nil { - t.Fatalf("unable to add to retribution store: %v", err) - } - // Ensure that the retribution store has one retribution. - if count := countRetributions(t, rs); count != 1 { + // Add all retributions, check that ForAll returns the correct + // information, and then remove all retributions. + testRetributionStoreAdds(frs, t, false) + testRetributionStoreForAll(frs, t, false) + testRetributionStoreRemoves(frs, t, false) +} + +func testRetributionStorePersistence(frs FailingRetributionStore, t *testing.T) { + // Make sure that a new retribution store is still emtpy after failing + // right off the bat. + frs.Restart() + if count := countRetributions(t, frs); count != 0 { t.Fatalf("expected 1 retributions, found %v", count) } - // Add second retribution state to the store. - if err := rs.Add(&retributions[1]); err != nil { - t.Fatalf("unable to add to retribution store: %v", err) + // Insert all retributions into the database, restarting and checking + // between subsequent calls to test that each intermediate additions are + // persisted. + testRetributionStoreAdds(frs, t, true) + + // After all retributions have been inserted, verify that the store + // emits a distinct set of retributions that are equivalent to the test + // vector. + testRetributionStoreForAll(frs, t, true) + + // Remove all retributions from the database, restarting and checking + // between subsequent calls to test that each intermediate removals are + // persisted. + testRetributionStoreRemoves(frs, t, true) +} + +func testRetributionStoreInit(frs FailingRetributionStore, t *testing.T) { + // Make sure that a new retribution store starts empty. + if count := countRetributions(t, frs); count != 0 { + t.Fatalf("expected 0 retributions, found %v", count) } - // There should be 2 retributions in the store. - if count := countRetributions(t, rs); count != 2 { +} + +func testRetributionStoreRemoveEmpty(frs FailingRetributionStore, t *testing.T) { + testRetributionStoreRemoves(frs, t, false) +} + +func testRetributionStoreOverwrite(frs FailingRetributionStore, t *testing.T) { + // Initially, add all retributions to store. + testRetributionStoreAdds(frs, t, false) + + // Overwrite the initial entries again. + for i, retInfo := range retributions { + if err := frs.Add(&retInfo); err != nil { + t.Fatalf("unable to add to retribution %v to store: %v", + i, err) + } + } + + // Check that retribution store still has 2 entries. + if count := countRetributions(t, frs); count != 2 { t.Fatalf("expected 2 retributions, found %v", count) } +} - // Retrieving the retribution states from the store should yield the same - // values as the originals. - rs.ForAll(func(ret *retributionInfo) error { - equal0 := reflect.DeepEqual(ret, &retributions[0]) - equal1 := reflect.DeepEqual(ret, &retributions[1]) - if !equal0 || !equal1 { - return errors.New("unexpected retribution retrieved from db") +func testRetributionStoreAdds( + frs FailingRetributionStore, + t *testing.T, + failing bool) { + + // Iterate over retributions, adding each from the store. If we are + // testing the store under failures, we restart the store and verify + // that the contents are the same. + for i, retInfo := range retributions { + // Snapshot number of entires before and after the addition. + nbefore := countRetributions(t, frs) + if err := frs.Add(&retInfo); err != nil { + t.Fatalf("unable to add to retribution %v to store: %v", + i, err) } - return nil - }) + nafter := countRetributions(t, frs) - // Remove the retribution states. - if err := rs.Remove(&retributions[0].chanPoint); err != nil { - t.Fatalf("unable to remove from retribution store: %v", err) + // Check that only one retribution was added. + if nafter-nbefore != 1 { + t.Fatalf("expected %v retributions, found %v", + nbefore+1, nafter) + } + + if failing { + frs.Restart() + + // Check that retribution store has persisted addition + // after restarting. + nrestart := countRetributions(t, frs) + if nrestart-nbefore != 1 { + t.Fatalf("expected %v retributions, found %v", + nbefore+1, nrestart) + } + } } - // Ensure that the retribution store has one retribution. - if count := countRetributions(t, rs); count != 1 { - t.Fatalf("expected 1 retributions, found %v", count) +} + +func testRetributionStoreRemoves( + frs FailingRetributionStore, + t *testing.T, + failing bool) { + + // Iterate over retributions, removing each from the store. If we are + // testing the store under failures, we restart the store and verify + // that the contents are the same. + for i, retInfo := range retributions { + // Snapshot number of entires before and after the removal. + nbefore := countRetributions(t, frs) + if err := frs.Remove(&retInfo.chanPoint); err != nil { + t.Fatalf("unable to remove to retribution %v "+ + "from store: %v", i, err) + } + nafter := countRetributions(t, frs) + + // If the store is empty, increment nbefore to simulate the + // removal of one element. + if nbefore == 0 { + nbefore++ + } + + // Check that only one retribution was removed. + if nbefore-nafter != 1 { + t.Fatalf("expected %v retributions, found %v", + nbefore-1, nafter) + } + + if failing { + frs.Restart() + + // Check that retribution store has persisted removal + // after restarting. + nrestart := countRetributions(t, frs) + if nbefore-nrestart != 1 { + t.Fatalf("expected %v retributions, found %v", + nbefore-1, nrestart) + } + } + } +} + +func testRetributionStoreForAll( + frs FailingRetributionStore, + t *testing.T, + failing bool) { + + // nrets is the number of retributions in the test vector + nrets := len(retributions) + + // isRestart indicates whether or not the database has been restarted. + // When testing for failures, this allows the test case to make a second + // attempt without causing a subsequent restart on the second pass. + var isRestart bool + +restartCheck: + // Construct a set of all channel points presented by the store. Entires + // are only be added to the set if their corresponding retribution + // infromation matches the test vector. + var foundSet = make(map[wire.OutPoint]struct{}) + + // Iterate through the stored retributions, checking to see if we have + // an equivalent retribution in the test vector. This will return an + // error unless all persisted retributions exist in the test vector. + if err := frs.ForAll(func(ret *retributionInfo) error { + // Fetch the retribution information from the test vector. If + // the entry does not exist, the test returns an error. + if exRetInfo, ok := retributionMap[ret.chanPoint]; ok { + // Compare the presented retribution information with + // the expected value, fail if they are inconsistent. + if !reflect.DeepEqual(ret, &exRetInfo) { + return fmt.Errorf("unexpected retribution "+ + "retrieved from db --\n"+ + "want: %#v\ngot: %#v", exRetInfo, ret, + ) + } + + // Retribution information from database matches the + // test vector, record the channel point in the found + // map. + foundSet[ret.chanPoint] = struct{}{} + + } else { + return fmt.Errorf("unkwown retribution "+ + "retrieved from db: %v", ret) + } + + return nil + }); err != nil { + t.Fatalf("failed to iterate over persistent retributions: %v", + err) } - if err := rs.Remove(&retributions[1].chanPoint); err != nil { - t.Fatalf("unable to remove from retribution store: %v", err) + // Check that retribution store emits nrets entires + if count := countRetributions(t, frs); count != nrets { + t.Fatalf("expected %v retributions, found %v", nrets, count) } - // Ensure that the retribution store is empty again. - if count := countRetributions(t, rs); count != 0 { - t.Fatalf("expected 0 retributions, found %v", count) + // Confirm that all of the retributions emitted from the iteration + // correspond to unique channel points. + nunique := len(foundSet) + if nunique != nrets { + t.Fatalf("expected %v unique retributions, only found %v", + nrets, nunique) + } + + // If in failure mode on only on first pass, restart the database and + // rexecute the test. + if failing && !isRestart { + frs.Restart() + isRestart = true + + goto restartCheck } } From 87ff292d3ab9514bd8ab97c834b99062fc262917 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Mon, 31 Jul 2017 23:17:13 -0700 Subject: [PATCH 11/15] lnd_test: test breach persistence after breach conf --- lnd_test.go | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 257 insertions(+), 1 deletion(-) diff --git a/lnd_test.go b/lnd_test.go index fe86aa02f23d..f19b6966e0cc 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -1751,6 +1751,9 @@ poll: return txid, nil } +// testRevokedCloseRetributinPostBreachConf tests that Alice is able carry out +// retribution in the event that she fails immediately after detecting Bob's +// breach txn in the mempool. func testRevokedCloseRetribution(net *networkHarness, t *harnessTest) { ctxb := context.Background() const ( @@ -1923,7 +1926,6 @@ func testRevokedCloseRetribution(net *networkHarness, t *harnessTest) { if err != nil { t.Fatalf("unable to find Bob's breach tx in mempool: %v", err) } - time.Sleep(100 * time.Millisecond) // Here, Alice sees Bob's breach transaction in the mempool, but is waiting // for it to confirm before continuing her retribution. We restart Alice to @@ -2005,6 +2007,256 @@ func testRevokedCloseRetribution(net *networkHarness, t *harnessTest) { } } +// testRevokedCloseRetributinPostBreachConf tests that Alice is able carry out +// retribution in the event that she fails immediately after receiving a +// confirmation of Bob's breach txn. +func testRevokedCloseRetributionPostBreachConf( + net *networkHarness, + t *harnessTest) { + + ctxb := context.Background() + const ( + timeout = time.Duration(time.Second * 5) + chanAmt = maxFundingAmount + paymentAmt = 10000 + numInvoices = 6 + ) + + // In order to test Alice's response to an uncooperative channel + // closure by Bob, we'll first open up a channel between them with a + // 0.5 BTC value. + ctxt, _ := context.WithTimeout(ctxb, timeout) + chanPoint := openChannelAndAssert(ctxt, t, net, net.Alice, net.Bob, + chanAmt, 0) + + // With the channel open, we'll create a few invoices for Bob that + // Alice will pay to in order to advance the state of the channel. + bobPaymentHashes := make([][]byte, numInvoices) + for i := 0; i < numInvoices; i++ { + preimage := bytes.Repeat([]byte{byte(192 - i)}, 32) + invoice := &lnrpc.Invoice{ + Memo: "testing", + RPreimage: preimage, + Value: paymentAmt, + } + resp, err := net.Bob.AddInvoice(ctxb, invoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) + } + + bobPaymentHashes[i] = resp.RHash + } + + // As we'll be querying the state of bob's channels frequently we'll + // create a closure helper function for the purpose. + getBobChanInfo := func() (*lnrpc.ActiveChannel, error) { + req := &lnrpc.ListChannelsRequest{} + bobChannelInfo, err := net.Bob.ListChannels(ctxb, req) + if err != nil { + return nil, err + } + if len(bobChannelInfo.Channels) != 1 { + t.Fatalf("bob should only have a single channel, instead he has %v", + len(bobChannelInfo.Channels)) + } + + return bobChannelInfo.Channels[0], nil + } + + // Wait for Alice to receive the channel edge from the funding manager. + ctxt, _ = context.WithTimeout(ctxb, timeout) + err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("alice didn't see the alice->bob channel before "+ + "timeout: %v", err) + } + + // Open up a payment stream to Alice that we'll use to send payment to + // Bob. We also create a small helper function to send payments to Bob, + // consuming the payment hashes we generated above. + alicePayStream, err := net.Alice.SendPayment(ctxb) + if err != nil { + t.Fatalf("unable to create payment stream for alice: %v", err) + } + sendPayments := func(start, stop int) error { + for i := start; i < stop; i++ { + sendReq := &lnrpc.SendRequest{ + PaymentHash: bobPaymentHashes[i], + Dest: net.Bob.PubKey[:], + Amt: paymentAmt, + } + if err := alicePayStream.Send(sendReq); err != nil { + return err + } + if resp, err := alicePayStream.Recv(); err != nil { + t.Fatalf("payment stream has been closed: %v", err) + } else if resp.PaymentError != "" { + t.Fatalf("error when attempting recv: %v", + resp.PaymentError) + } + } + return nil + } + + // Send payments from Alice to Bob using 3 of Bob's payment hashes + // generated above. + if err := sendPayments(0, numInvoices/2); err != nil { + t.Fatalf("unable to send payment: %v", err) + } + + // Next query for Bob's channel state, as we sent 3 payments of 10k + // satoshis each, Bob should now see his balance as being 30k satoshis. + time.Sleep(time.Millisecond * 200) + bobChan, err := getBobChanInfo() + if err != nil { + t.Fatalf("unable to get bob's channel info: %v", err) + } + if bobChan.LocalBalance != 30000 { + t.Fatalf("bob's balance is incorrect, got %v, expected %v", + bobChan.LocalBalance, 30000) + } + + // Grab Bob's current commitment height (update number), we'll later + // revert him to this state after additional updates to force him to + // broadcast this soon to be revoked state. + bobStateNumPreCopy := bobChan.NumUpdates + + // Create a temporary file to house Bob's database state at this + // particular point in history. + bobTempDbPath, err := ioutil.TempDir("", "bob-past-state") + if err != nil { + t.Fatalf("unable to create temp db folder: %v", err) + } + bobTempDbFile := filepath.Join(bobTempDbPath, "channel.db") + defer os.Remove(bobTempDbPath) + + // With the temporary file created, copy Bob's current state into the + // temporary file we created above. Later after more updates, we'll + // restore this state. + bobDbPath := filepath.Join(net.Bob.cfg.DataDir, "simnet/bitcoin/channel.db") + if err := copyFile(bobTempDbFile, bobDbPath); err != nil { + t.Fatalf("unable to copy database files: %v", err) + } + + // Finally, send payments from Alice to Bob, consuming Bob's remaining + // payment hashes. + if err := sendPayments(numInvoices/2, numInvoices); err != nil { + t.Fatalf("unable to send payment: %v", err) + } + + bobChan, err = getBobChanInfo() + if err != nil { + t.Fatalf("unable to get bob chan info: %v", err) + } + + // Now we shutdown Bob, copying over the his temporary database state + // which has the *prior* channel state over his current most up to date + // state. With this, we essentially force Bob to travel back in time + // within the channel's history. + if err = net.RestartNode(net.Bob, func() error { + return os.Rename(bobTempDbFile, bobDbPath) + }); err != nil { + t.Fatalf("unable to restart node: %v", err) + } + + // Now query for Bob's channel state, it should show that he's at a + // state number in the past, not the *latest* state. + bobChan, err = getBobChanInfo() + if err != nil { + t.Fatalf("unable to get bob chan info: %v", err) + } + if bobChan.NumUpdates != bobStateNumPreCopy { + t.Fatalf("db copy failed: %v", bobChan.NumUpdates) + } + + // Now force Bob to execute a *force* channel closure by unilaterally + // broadcasting his current channel state. This is actually the + // commitment transaction of a prior *revoked* state, so he'll soon + // feel the wrath of Alice's retribution. + force := true + closeUpdates, _, err := net.CloseChannel(ctxb, net.Bob, chanPoint, force) + if err != nil { + t.Fatalf("unable to close channel: %v", err) + } + + // Finally, generate a single block, wait for the final close status + // update, then ensure that the closing transaction was included in the + // block. + block := mineBlocks(t, net, 1)[0] + + // Here, Alice receives a confirmation of Bob's breach transaction. We + // restart Alice to ensure that she is persisting her retribution state and + // continues exacting justice after her node restarts. + if err := net.RestartNode(net.Alice, nil); err != nil { + t.Fatalf("unable to stop Alice's node: %v", err) + } + + breachTXID, err := net.WaitForChannelClose(ctxb, closeUpdates) + if err != nil { + t.Fatalf("error while waiting for channel close: %v", err) + } + assertTxInBlock(t, block, breachTXID) + + // Query the mempool for Alice's justice transaction, this should be + // broadcast as Bob's contract breaching transaction gets confirmed + // above. + justiceTXID, err := waitForTxInMempool(net.Miner.Node, 5*time.Second) + if err != nil { + t.Fatalf("unable to find Alice's justice tx in mempool: %v", err) + } + time.Sleep(100 * time.Millisecond) + + // Query for the mempool transaction found above. Then assert that all + // the inputs of this transaction are spending outputs generated by + // Bob's breach transaction above. + justiceTx, err := net.Miner.Node.GetRawTransaction(justiceTXID) + if err != nil { + t.Fatalf("unable to query for justice tx: %v", err) + } + for _, txIn := range justiceTx.MsgTx().TxIn { + if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) { + t.Fatalf("justice tx not spending commitment utxo "+ + "instead is: %v", txIn.PreviousOutPoint) + } + } + + // We restart Alice here to ensure that she persists her retribution state + // and successfully continues exacting retribution after restarting. At + // this point, Alice has broadcast the justice transaction, but it hasn't + // been confirmed yet; when Alice restarts, she should start waiting for + // the justice transaction to confirm again. + if err := net.RestartNode(net.Alice, nil); err != nil { + t.Fatalf("unable to restart Alice's node: %v", err) + } + + // Now mine a block, this transaction should include Alice's justice + // transaction which was just accepted into the mempool. + block = mineBlocks(t, net, 1)[0] + + // The block should have exactly *two* transactions, one of which is + // the justice transaction. + if len(block.Transactions) != 2 { + t.Fatalf("transaction wasn't mined") + } + justiceSha := block.Transactions[1].TxHash() + if !bytes.Equal(justiceTx.Hash()[:], justiceSha[:]) { + t.Fatalf("justice tx wasn't mined") + } + + // Finally, obtain Alice's channel state, she shouldn't report any + // channel as she just successfully brought Bob to justice by sweeping + // all the channel funds. + req := &lnrpc.ListChannelsRequest{} + aliceChanInfo, err := net.Alice.ListChannels(ctxb, req) + if err != nil { + t.Fatalf("unable to query for alice's channels: %v", err) + } + if len(aliceChanInfo.Channels) != 0 { + t.Fatalf("alice shouldn't have a channel: %v", + spew.Sdump(aliceChanInfo.Channels)) + } +} + func testHtlcErrorPropagation(net *networkHarness, t *harnessTest) { // In this test we wish to exercise the daemon's correct parsing, // handling, and propagation of errors that occur while processing a @@ -3190,6 +3442,10 @@ var testsCases = []*testCase{ name: "revoked uncooperative close retribution", test: testRevokedCloseRetribution, }, + { + name: "revoked uncooperative close retribution post breach conf", + test: testRevokedCloseRetributionPostBreachConf, + }, } // TestLightningNetworkDaemon performs a series of integration tests amongst a From 3c05c1299c562653dd57aec485ad021756a4dd71 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Mon, 31 Jul 2017 18:42:32 -0700 Subject: [PATCH 12/15] lnwallet: move latest SignDescriptor + serialization --- lnwallet/interface.go | 60 ----------------------- lnwallet/signdescriptor.go | 85 +++++++++++++++++++++++++++------ lnwallet/signdescriptor_test.go | 4 +- 3 files changed, 72 insertions(+), 77 deletions(-) diff --git a/lnwallet/interface.go b/lnwallet/interface.go index 92f6adacbb9a..94d4d0de3a01 100644 --- a/lnwallet/interface.go +++ b/lnwallet/interface.go @@ -224,66 +224,6 @@ type BlockChainIO interface { GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) } -// SignDescriptor houses the necessary information required to successfully sign -// a given output. This struct is used by the Signer interface in order to gain -// access to critical data needed to generate a valid signature. -type SignDescriptor struct { - // Pubkey is the public key to which the signature should be generated - // over. The Signer should then generate a signature with the private - // key corresponding to this public key. - PubKey *btcec.PublicKey - - // SingleTweak is a scalar value that will be added to the private key - // corresponding to the above public key to obtain the private key to - // be used to sign this input. This value is typically derived via the - // following computation: - // - // * derivedKey = privkey + sha256(perCommitmentPoint || pubKey) mod N - // - // NOTE: If this value is nil, then the input can be signed using only - // the above public key. Either a SingleTweak should be set or a - // DoubleTweak, not both. - SingleTweak []byte - - // DoubleTweak is a private key that will be used in combination with - // its corresponding private key to derive the private key that is to - // be used to sign the target input. Within the Lightning protocol, - // this value is typically the commitment secret from a previously - // revoked commitment transaction. This value is in combination with - // two hash values, and the original private key to derive the private - // key to be used when signing. - // - // * k = (privKey*sha256(pubKey || tweakPub) + - // tweakPriv*sha256(tweakPub || pubKey)) mod N - // - // NOTE: If this value is nil, then the input can be signed using only - // the above public key. Either a SingleTweak should be set or a - // DoubleTweak, not both. - DoubleTweak *btcec.PrivateKey - - // WitnessScript is the full script required to properly redeem the - // output. This field will only be populated if a p2wsh or a p2sh - // output is being signed. - WitnessScript []byte - - // Output is the target output which should be signed. The PkScript and - // Value fields within the output should be properly populated, - // otherwise an invalid signature may be generated. - Output *wire.TxOut - - // HashType is the target sighash type that should be used when - // generating the final sighash, and signature. - HashType txscript.SigHashType - - // SigHashes is the pre-computed sighash midstate to be used when - // generating the final sighash for signing. - SigHashes *txscript.TxSigHashes - - // InputIndex is the target input within the transaction that should be - // signed. - InputIndex int -} - // Signer represents an abstract object capable of generating raw signatures as // well as full complete input scripts given a valid SignDescriptor and // transaction. This interface fully abstracts away signing paving the way for diff --git a/lnwallet/signdescriptor.go b/lnwallet/signdescriptor.go index 8e1ff3918f86..08c24e5d2a87 100644 --- a/lnwallet/signdescriptor.go +++ b/lnwallet/signdescriptor.go @@ -2,6 +2,7 @@ package lnwallet import ( "encoding/binary" + "errors" "io" "github.com/lightningnetwork/lnd/lnwire" @@ -10,6 +11,12 @@ import ( "github.com/roasbeef/btcd/wire" ) +var ( + // ErrTweakOverdose signals a SignDescriptor is invalid because both of its + // SingleTweak and DoubleTweak are non-nil. + ErrTweakOverdose = errors.New("sign descriptor should only have one tweak") +) + // SignDescriptor houses the necessary information required to successfully sign // a given output. This struct is used by the Signer interface in order to gain // access to critical data needed to generate a valid signature. @@ -19,14 +26,33 @@ type SignDescriptor struct { // key corresponding to this public key. PubKey *btcec.PublicKey - // PrivateTweak is a scalar value that should be added to the private - // key corresponding to the above public key to obtain the private key - // to be used to sign this input. This value is typically a leaf node - // from the revocation tree. + // SingleTweak is a scalar value that will be added to the private key + // corresponding to the above public key to obtain the private key to + // be used to sign this input. This value is typically derived via the + // following computation: + // + // * derivedKey = privkey + sha256(perCommitmentPoint || pubKey) mod N + // + // NOTE: If this value is nil, then the input can be signed using only + // the above public key. Either a SingleTweak should be set or a + // DoubleTweak, not both. + SingleTweak []byte + + // DoubleTweak is a private key that will be used in combination with + // its corresponding private key to derive the private key that is to + // be used to sign the target input. Within the Lightning protocol, + // this value is typically the commitment secret from a previously + // revoked commitment transaction. This value is in combination with + // two hash values, and the original private key to derive the private + // key to be used when signing. + // + // * k = (privKey*sha256(pubKey || tweakPub) + + // tweakPriv*sha256(tweakPub || pubKey)) mod N // // NOTE: If this value is nil, then the input can be signed using only - // the above public key. - PrivateTweak []byte + // the above public key. Either a SingleTweak should be set or a + // DoubleTweak, not both. + DoubleTweak *btcec.PrivateKey // WitnessScript is the full script required to properly redeem the // output. This field will only be populated if a p2wsh or a p2sh @@ -62,7 +88,15 @@ func WriteSignDescriptor(w io.Writer, sd *SignDescriptor) error { return err } - if err := wire.WriteVarBytes(w, 0, sd.PrivateTweak); err != nil { + if err := wire.WriteVarBytes(w, 0, sd.SingleTweak); err != nil { + return err + } + + var doubleTweakBytes []byte + if sd.DoubleTweak != nil { + doubleTweakBytes = sd.DoubleTweak.Serialize() + } + if err := wire.WriteVarBytes(w, 0, doubleTweakBytes); err != nil { return err } @@ -96,20 +130,41 @@ func ReadSignDescriptor(r io.Reader, sd *SignDescriptor) error { return err } - privateTweak, err := wire.ReadVarBytes(r, 0, 32, "privateTweak") + singleTweak, err := wire.ReadVarBytes(r, 0, 32, "singleTweak") if err != nil { return err } - // Serializing a SignDescriptor with a nil-valued PrivateTweak results in - // deserializing a zero-length slice. Since a nil-valued PrivateTweak has - // special meaning and a zero-length slice for a PrivateTweak is invalid, + // Serializing a SignDescriptor with a nil-valued SingleTweak results in + // deserializing a zero-length slice. Since a nil-valued SingleTweak has + // special meaning and a zero-length slice for a SingleTweak is invalid, // we can use the zero-length slice as the flag for a nil-valued - // PrivateTweak. - if len(privateTweak) == 0 { - sd.PrivateTweak = nil + // SingleTweak. + if len(singleTweak) == 0 { + sd.SingleTweak = nil } else { - sd.PrivateTweak = privateTweak + sd.SingleTweak = singleTweak + } + + doubleTweakBytes, err := wire.ReadVarBytes(r, 0, 32, "doubleTweak") + if err != nil { + return err + } + + // Serializing a SignDescriptor with a nil-valued DoubleTweak results in + // deserializing a zero-length slice. Since a nil-valued DoubleTweak has + // special meaning and a zero-length slice for a DoubleTweak is invalid, + // we can use the zero-length slice as the flag for a nil-valued + // DoubleTweak. + if len(doubleTweakBytes) == 0 { + sd.DoubleTweak = nil + } else { + sd.DoubleTweak, _ = btcec.PrivKeyFromBytes(btcec.S256(), doubleTweakBytes) + } + + // Only one tweak should ever be set, fail if both are present. + if sd.SingleTweak != nil && sd.DoubleTweak != nil { + return ErrTweakOverdose } witnessScript, err := wire.ReadVarBytes(r, 0, 100, "witnessScript") diff --git a/lnwallet/signdescriptor_test.go b/lnwallet/signdescriptor_test.go index ddd22f34dc38..4225665fb6f7 100644 --- a/lnwallet/signdescriptor_test.go +++ b/lnwallet/signdescriptor_test.go @@ -34,7 +34,7 @@ func TestSignDescriptorSerialization(t *testing.T) { signDescriptors := []SignDescriptor{ { - PrivateTweak: []byte{ + SingleTweak: []byte{ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, @@ -66,7 +66,7 @@ func TestSignDescriptorSerialization(t *testing.T) { // Test serializing a SignDescriptor with a nil-valued PrivateTweak { - PrivateTweak: nil, + SingleTweak: nil, WitnessScript: []byte{ 0x00, 0x14, 0xee, 0x91, 0x41, 0x7e, 0x85, 0x6c, 0xde, 0x10, 0xa2, 0x91, 0x1e, 0xdc, 0xbd, 0xbd, 0x69, 0xe2, From 66c303a921b7487584c8322988c0911a741a9c46 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Mon, 31 Jul 2017 18:43:22 -0700 Subject: [PATCH 13/15] utxonursery: refactors kidOutput SignDescriptor serialization --- utxonursery.go | 54 +++----------------------------------------------- 1 file changed, 3 insertions(+), 51 deletions(-) diff --git a/utxonursery.go b/utxonursery.go index b4a1b196c27d..4d016cc0efbe 100644 --- a/utxonursery.go +++ b/utxonursery.go @@ -784,7 +784,7 @@ func fetchGraduatingOutputs(db *channeldb.DB, wallet *lnwallet.LightningWallet, // our commitment transaction or theirs, and also if it's an HTLC // output or not. for _, kgtnOutput := range kgtnOutputs { - kgtnOutput.witnessFunc = kgtnOutput.witnessType.generateFunc( + kgtnOutput.witnessFunc = kgtnOutput.witnessType.GenWitnessFunc( &wallet.Cfg.Signer, kgtnOutput.signDescriptor, ) } @@ -1011,25 +1011,7 @@ func serializeKidOutput(w io.Writer, kid *kidOutput) error { return err } - if err := lnwallet.WriteSignDescriptor(w, kid.signDescriptor); err != nil { - return err - } - - if err := wire.WriteVarBytes(w, 0, kid.signDescriptor.SingleTweak); err != nil { - return err - } - - if err := wire.WriteVarBytes(w, 0, kid.signDescriptor.WitnessScript); err != nil { - return err - } - - if err := writeTxOut(w, kid.signDescriptor.Output); err != nil { - return err - } - - byteOrder.PutUint32(scratch[:4], uint32(kid.signDescriptor.HashType)) - _, err := w.Write(scratch[:4]) - return err + return lnwallet.WriteSignDescriptor(w, kid.signDescriptor) } // deserializeKidOutput takes a byte array representation of a kidOutput @@ -1072,37 +1054,7 @@ func deserializeKidOutput(r io.Reader) (*kidOutput, error) { kid.witnessType = lnwallet.WitnessType(byteOrder.Uint16(scratch[:2])) kid.signDescriptor = &lnwallet.SignDescriptor{} - - descKeyBytes, err := wire.ReadVarBytes(r, 0, 34, "descKeyBytes") - if err != nil { - return nil, err - } - - descKey, err := btcec.ParsePubKey(descKeyBytes, btcec.S256()) - if err != nil { - return nil, err - } - kid.signDescriptor.PubKey = descKey - - descPrivateTweak, err := wire.ReadVarBytes(r, 0, 32, "privateTweak") - if err != nil { - return nil, err - } - kid.signDescriptor.SingleTweak = descPrivateTweak - - descWitnessScript, err := wire.ReadVarBytes(r, 0, 100, "witnessScript") - if err != nil { - return nil, err - } - kid.signDescriptor.WitnessScript = descWitnessScript - - descTxOut := &wire.TxOut{} - if err := readTxOut(r, descTxOut); err != nil { - return nil, err - } - kid.signDescriptor.Output = descTxOut - - if _, err := r.Read(scratch[:4]); err != nil { + if err := lnwallet.ReadSignDescriptor(r, kid.signDescriptor); err != nil { return nil, err } From 4f771e732dd6273ed55809e9c58b359d7a920086 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Mon, 14 Aug 2017 15:14:43 -0700 Subject: [PATCH 14/15] breacharbiter: makes newline formatting consistent with lnd --- breacharbiter.go | 55 +++++++++++++++--------------------------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/breacharbiter.go b/breacharbiter.go index ffad00adc91f..16d4282e9655 100644 --- a/breacharbiter.go +++ b/breacharbiter.go @@ -155,7 +155,7 @@ func (b *breachArbiter) Start() error { nActive := len(activeChannels) if nActive > 0 { brarLog.Infof("Retrieved %v channels from database, watching "+ - "with vigilance!", len(activeChannels)) + "with vigilance!", nActive) } // Here we will determine a set of channels that will need to be managed @@ -163,25 +163,23 @@ func (b *breachArbiter) Start() error { // disk, we will create a channel state machine that can be used to // watch for any potential channel closures. We must first exclude any // channel whose retribution process has been initiated, and proceed to - // mark them as closed. - // The state machines generated for these filtered channels can be - // discarded, as their fate will be placed in the hands of an - // exactRetribution task spawned later. + // mark them as closed. The state machines generated for these filtered + // channels can be discarded, as their fate will be placed in the hands + // of an exactRetribution task spawned later. // // NOTE Spawning of the exactRetribution task is intentionally postponed // until after this step in order to ensure that the all breached // channels are reflected as closed in channeldb and consistent with // what is checkpointed by the breach arbiter. Instead of treating the // breached-and-closed and breached-but-still-active channels as - // separate sets of channels, we first - // ensure that all breach-but-still-active channels are promoted to + // separate sets of channels, we first ensure that all + // breached-but-still-active channels are promoted to // breached-and-closed during restart, allowing us to treat them as a // single set from here on out. This approach also has the added benefit // of minimizing the likelihood that the wrong number of tasks are // spawned per breached channel, and prevents us from being in a - // position where - // retribution has completed but the channel is still marked as open in - // channeldb. + // position where retribution has completed but the channel is still + // marked as open in channeldb. channelsToWatch := make([]*lnwallet.LightningChannel, 0, nActive) for _, chanState := range activeChannels { // Initialize active channel from persisted channel state. @@ -229,8 +227,6 @@ func (b *breachArbiter) Start() error { // channels to watch. channelsToWatch = append(channelsToWatch, channel) } - // Trim channels in the event that some were filtered. - channelsToWatch = channelsToWatch[:] // TODO(roasbeef): instead use closure height of channel _, currentHeight, err := b.chainIO.GetBestBlock() @@ -510,8 +506,7 @@ func (b *breachArbiter) exactRetribution( return } - brarLog.Debugf( - "Broadcasting justice tx: %v", + brarLog.Debugf("Broadcasting justice tx: %v", newLogClosure(func() string { return spew.Sdump(justiceTx) })) @@ -599,8 +594,8 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, chanPoint := contract.ChannelPoint() - brarLog.Debugf( - "Breach observer for ChannelPoint(%v) started", chanPoint) + brarLog.Debugf("Breach observer for ChannelPoint(%v) started", + chanPoint) select { // A read from this channel indicates that the contract has been @@ -633,12 +628,8 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, // // TODO(roasbeef): also notify utxoNursery, might've had // outbound HTLC's in flight - go waitForChanToClose( - uint32(closeInfo.SpendingHeight), - b.notifier, - nil, - chanPoint, - closeInfo.SpenderTxHash, + go waitForChanToClose(uint32(closeInfo.SpendingHeight), + b.notifier, nil, chanPoint, closeInfo.SpenderTxHash, func() { // As we just detected a channel was closed via // a unilateral commitment broadcast by the @@ -780,8 +771,8 @@ func (b *breachArbiter) breachObserver(contract *lnwallet.LightningChannel, IsPending: true, } if err := contract.DeleteState(closeInfo); err != nil { - brarLog.Errorf( - "unable to delete channel state: %v", err) + brarLog.Errorf("unable to delete channel state: %v", + err) } // Finally, we send the retribution information into the @@ -984,7 +975,6 @@ func (b *breachArbiter) craftCommitSweepTx( // RetributionStore should use appropriate synchronization primitives, or // be otherwise safe for concurrent access. type RetributionStore interface { - // Add persists the retributionInfo to disk, using the information's // chanPoint as the key. This method should overwrite any existing // entires found under the same key, and an error should be raised if @@ -1037,14 +1027,7 @@ func (rs *retributionStore) Add(ret *retributionInfo) error { return err } - if err := retBucket.Put( - outBuf.Bytes(), - retBuf.Bytes(), - ); err != nil { - return err - } - - return nil + return retBucket.Put(outBuf.Bytes(), retBuf.Bytes()) }) } @@ -1067,11 +1050,7 @@ func (rs *retributionStore) Remove(key *wire.OutPoint) error { return err } - if err := retBucket.Delete(outBuf.Bytes()); err != nil { - return err - } - - return nil + return retBucket.Delete(outBuf.Bytes()) }) } From bf5d4b228e0a8a517fca6332c3139e9f77903259 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Mon, 14 Aug 2017 15:45:32 -0700 Subject: [PATCH 15/15] breacharbiter_test: improves documentation for ba test cases --- breacharbiter_test.go | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/breacharbiter_test.go b/breacharbiter_test.go index e8c6afbb5da5..1f83cbfa94eb 100644 --- a/breacharbiter_test.go +++ b/breacharbiter_test.go @@ -559,8 +559,11 @@ func countRetributions(t *testing.T, rs RetributionStore) int { return count } -// testRetributionStore executes a generic test suite for any concrete -// implementation of the RetributionStore interface. +// testRetributionStoreAddRemove executes a generic test suite for any concrete +// implementation of the RetributionStore interface. This test adds all +// retributions to the store, confirms that they are all present, and then +// removes each one individually. Between each addition or removal, the number +// of elements in the store is checked to ensure that it only changes by one. func testRetributionStoreAddRemove(frs FailingRetributionStore, t *testing.T) { // Make sure that a new retribution store is actually emtpy. if count := countRetributions(t, frs); count != 0 { @@ -574,6 +577,9 @@ func testRetributionStoreAddRemove(frs FailingRetributionStore, t *testing.T) { testRetributionStoreRemoves(frs, t, false) } +// testRetributionStorePersistence executes the same general test as +// testRetributionStoreAddRemove, except that it also restarts the store between +// each operation to ensure that the results are properly persisted. func testRetributionStorePersistence(frs FailingRetributionStore, t *testing.T) { // Make sure that a new retribution store is still emtpy after failing // right off the bat. @@ -598,6 +604,8 @@ func testRetributionStorePersistence(frs FailingRetributionStore, t *testing.T) testRetributionStoreRemoves(frs, t, true) } +// testRetributionStoreInit ensures that a retribution store is always +// initialized with no retributions. func testRetributionStoreInit(frs FailingRetributionStore, t *testing.T) { // Make sure that a new retribution store starts empty. if count := countRetributions(t, frs); count != 0 { @@ -605,10 +613,15 @@ func testRetributionStoreInit(frs FailingRetributionStore, t *testing.T) { } } +// testRetributionStoreRemoveEmpty ensures that a retribution store will not +// fail or panic if it is instructed to remove an entry while empty. func testRetributionStoreRemoveEmpty(frs FailingRetributionStore, t *testing.T) { testRetributionStoreRemoves(frs, t, false) } +// testRetributionStoreOverwrite ensures that attempts to write retribution +// information regarding a channel point that already exists does not change the +// total number of entries held by the retribution store. func testRetributionStoreOverwrite(frs FailingRetributionStore, t *testing.T) { // Initially, add all retributions to store. testRetributionStoreAdds(frs, t, false) @@ -627,6 +640,10 @@ func testRetributionStoreOverwrite(frs FailingRetributionStore, t *testing.T) { } } +// testRetributionStoreAdds adds all of the test retributions to the database, +// ensuring that the total number of elements increases by exactly 1 after each +// operation. If the `failing` flag is provide, the test will restart the +// database and confirm that the delta is still 1. func testRetributionStoreAdds( frs FailingRetributionStore, t *testing.T, @@ -664,6 +681,10 @@ func testRetributionStoreAdds( } } +// testRetributionStoreRemoves removes all of the test retributions to the +// database, ensuring that the total number of elements decreases by exactly 1 +// after each operation. If the `failing` flag is provide, the test will +// restart the database and confirm that the delta is the same. func testRetributionStoreRemoves( frs FailingRetributionStore, t *testing.T, @@ -707,6 +728,11 @@ func testRetributionStoreRemoves( } } +// testRetributionStoreForAll iterates over the current entries in the +// retribution store, ensuring that each entry in the database is unique, and +// corresponds to exactly one of the entries in the test vector. If the +// `failing` flag is provide, the test will restart the database and confirm +// that the entries again validate against the test vectors. func testRetributionStoreForAll( frs FailingRetributionStore, t *testing.T,