watchtower/lookout/lookout_test: adds simple lookout tests
This commit is contained in:
parent
671be69743
commit
fa63ba2e37
249
watchtower/lookout/lookout_test.go
Normal file
249
watchtower/lookout/lookout_test.go
Normal file
@ -0,0 +1,249 @@
|
||||
// +build dev
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
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 := wtdb.NewMockDB()
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Create two sessions, representing two distinct clients.
|
||||
sessionInfo1 := &wtdb.SessionInfo{
|
||||
ID: makeArray33(1),
|
||||
MaxUpdates: 10,
|
||||
RewardAddress: makeAddrSlice(22),
|
||||
}
|
||||
sessionInfo2 := &wtdb.SessionInfo{
|
||||
ID: makeArray33(2),
|
||||
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),
|
||||
}
|
||||
|
||||
// Encrypt the first justice kit under the txid of the first txn.
|
||||
encBlob1, err := blob1.Encrypt(hash1[:], 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to encrypt sweep detail 1: %v", err)
|
||||
}
|
||||
|
||||
// Encrypt the second justice kit under the txid of the second txn.
|
||||
encBlob2, err := blob2.Encrypt(hash2[:], 0)
|
||||
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: wtdb.NewBreachHintFromHash(&hash1),
|
||||
EncryptedBlob: encBlob1,
|
||||
SeqNum: 1,
|
||||
}
|
||||
txBlob2 := &wtdb.SessionStateUpdate{
|
||||
ID: makeArray33(2),
|
||||
Hint: wtdb.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):
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user