lnd.xprv/breacharbiter_test.go
Philip Hayes b19c483a04 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.
2017-08-25 17:56:50 -07:00

399 lines
11 KiB
Go

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