lnd.xprv/watchtower/lookout/lookout_test.go
Conner Fromknecht 04e1e2298f
watchtower: remove dev tag from unit test files
Several watchtower unit tests were not being executed by make unit since
they require the dev tag to be passed in. All tests still pass.
2020-08-20 12:12:51 -07:00

269 lines
6.9 KiB
Go

package lookout_test
import (
"bytes"
"crypto/rand"
"encoding/binary"
"io"
"testing"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/lookout"
"github.com/lightningnetwork/lnd/watchtower/wtdb"
"github.com/lightningnetwork/lnd/watchtower/wtmock"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
)
type mockPunisher struct {
matches chan *lookout.JusticeDescriptor
}
func (p *mockPunisher) Punish(
info *lookout.JusticeDescriptor, quit <-chan struct{}) error {
p.matches <- info
return nil
}
func makeArray32(i uint64) [32]byte {
var arr [32]byte
binary.BigEndian.PutUint64(arr[:], i)
return arr
}
func makeArray33(i uint64) [33]byte {
var arr [33]byte
binary.BigEndian.PutUint64(arr[:], i)
return arr
}
func makePubKey(i uint64) [33]byte {
var arr [33]byte
arr[0] = 0x02
if i%2 == 1 {
arr[0] |= 0x01
}
binary.BigEndian.PutUint64(arr[1:], i)
return arr
}
func makeArray64(i uint64) [64]byte {
var arr [64]byte
binary.BigEndian.PutUint64(arr[:], i)
return arr
}
func makeAddrSlice(size int) []byte {
addr := make([]byte, size)
if _, err := io.ReadFull(rand.Reader, addr); err != nil {
panic("cannot make addr")
}
return addr
}
func TestLookoutBreachMatching(t *testing.T) {
db := wtmock.NewTowerDB()
// Initialize an mock backend to feed the lookout blocks.
backend := lookout.NewMockBackend()
// Initialize a punisher that will feed any successfully constructed
// justice descriptors across the matches channel.
matches := make(chan *lookout.JusticeDescriptor)
punisher := &mockPunisher{matches: matches}
// With the resources in place, initialize and start our watcher.
watcher := lookout.New(&lookout.Config{
BlockFetcher: backend,
DB: db,
EpochRegistrar: backend,
Punisher: punisher,
})
if err := watcher.Start(); err != nil {
t.Fatalf("unable to start watcher: %v", err)
}
rewardAndCommitType := blob.TypeFromFlags(
blob.FlagReward, blob.FlagCommitOutputs,
)
// Create two sessions, representing two distinct clients.
sessionInfo1 := &wtdb.SessionInfo{
ID: makeArray33(1),
Policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{
BlobType: rewardAndCommitType,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 10,
},
RewardAddress: makeAddrSlice(22),
}
sessionInfo2 := &wtdb.SessionInfo{
ID: makeArray33(2),
Policy: wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{
BlobType: rewardAndCommitType,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
},
MaxUpdates: 10,
},
RewardAddress: makeAddrSlice(22),
}
// Insert both sessions into the watchtower's database.
err := db.InsertSessionInfo(sessionInfo1)
if err != nil {
t.Fatalf("unable to insert session info: %v", err)
}
err = db.InsertSessionInfo(sessionInfo2)
if err != nil {
t.Fatalf("unable to insert session info: %v", err)
}
// Construct two distinct transactions, that will be used to test the
// breach hint matching.
tx := wire.NewMsgTx(wire.TxVersion)
hash1 := tx.TxHash()
tx2 := wire.NewMsgTx(wire.TxVersion + 1)
hash2 := tx2.TxHash()
if bytes.Equal(hash1[:], hash2[:]) {
t.Fatalf("breach txids should be different")
}
// Construct a justice kit for each possible breach transaction.
blob1 := &blob.JusticeKit{
SweepAddress: makeAddrSlice(22),
RevocationPubKey: makePubKey(1),
LocalDelayPubKey: makePubKey(1),
CSVDelay: 144,
CommitToLocalSig: makeArray64(1),
}
blob2 := &blob.JusticeKit{
SweepAddress: makeAddrSlice(22),
RevocationPubKey: makePubKey(2),
LocalDelayPubKey: makePubKey(2),
CSVDelay: 144,
CommitToLocalSig: makeArray64(2),
}
key1 := blob.NewBreachKeyFromHash(&hash1)
key2 := blob.NewBreachKeyFromHash(&hash2)
// Encrypt the first justice kit under breach key one.
encBlob1, err := blob1.Encrypt(key1, blob.FlagCommitOutputs.Type())
if err != nil {
t.Fatalf("unable to encrypt sweep detail 1: %v", err)
}
// Encrypt the second justice kit under breach key two.
encBlob2, err := blob2.Encrypt(key2, blob.FlagCommitOutputs.Type())
if err != nil {
t.Fatalf("unable to encrypt sweep detail 2: %v", err)
}
// Add both state updates to the tower's database.
txBlob1 := &wtdb.SessionStateUpdate{
ID: makeArray33(1),
Hint: blob.NewBreachHintFromHash(&hash1),
EncryptedBlob: encBlob1,
SeqNum: 1,
}
txBlob2 := &wtdb.SessionStateUpdate{
ID: makeArray33(2),
Hint: blob.NewBreachHintFromHash(&hash2),
EncryptedBlob: encBlob2,
SeqNum: 1,
}
if _, err := db.InsertStateUpdate(txBlob1); err != nil {
t.Fatalf("unable to add tx to db: %v", err)
}
if _, err := db.InsertStateUpdate(txBlob2); err != nil {
t.Fatalf("unable to add tx to db: %v", err)
}
// Create a block containing the first transaction, connecting this
// block should match the first state update's breach hint.
block := &wire.MsgBlock{
Header: wire.BlockHeader{
Nonce: 1,
},
Transactions: []*wire.MsgTx{tx},
}
blockHash := block.BlockHash()
epoch := &chainntnfs.BlockEpoch{
Hash: &blockHash,
Height: 1,
}
// Connect the block via our mock backend.
backend.ConnectEpoch(epoch, block)
// This should trigger dispatch of the justice kit for the first tx.
select {
case match := <-matches:
txid := match.BreachedCommitTx.TxHash()
if !bytes.Equal(txid[:], hash1[:]) {
t.Fatalf("matched breach did not match tx1's txid")
}
case <-time.After(5 * time.Second):
t.Fatalf("breach tx1 was not matched")
}
// Ensure that at most one txn was matched as a result of connecting the
// first block.
select {
case <-matches:
t.Fatalf("only one txn should have been matched")
case <-time.After(50 * time.Millisecond):
}
// Now, construct a second block containing the second breach
// transaction.
block2 := &wire.MsgBlock{
Header: wire.BlockHeader{
Nonce: 2,
},
Transactions: []*wire.MsgTx{tx2},
}
blockHash2 := block2.BlockHash()
epoch2 := &chainntnfs.BlockEpoch{
Hash: &blockHash2,
Height: 2,
}
// Verify that the block hashes do no collide, otherwise the mock
// backend may not function properly.
if bytes.Equal(blockHash[:], blockHash2[:]) {
t.Fatalf("block hashes should be different")
}
// Connect the second block, such that the block is delivered via the
// epoch stream.
backend.ConnectEpoch(epoch2, block2)
// This should trigger dispatch of the justice kit for the second txn.
select {
case match := <-matches:
txid := match.BreachedCommitTx.TxHash()
if !bytes.Equal(txid[:], hash2[:]) {
t.Fatalf("received breach did not match tx2's txid")
}
case <-time.After(5 * time.Second):
t.Fatalf("tx was not matched")
}
// Ensure that at most one txn was matched as a result of connecting the
// second block.
select {
case <-matches:
t.Fatalf("only one txn should have been matched")
case <-time.After(50 * time.Millisecond):
}
}