mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-07-28 13:52:55 +02:00
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.
This commit is contained in:
committed by
Olaoluwa Osuntokun
parent
6ffe33f01a
commit
c3736e6893
@@ -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
|
||||
t.Fatalf("unable to open channeldb: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
cleanUp := func() {
|
||||
if db != nil {
|
||||
db.Close()
|
||||
}
|
||||
os.RemoveAll(tempDirName)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user