Skip to content

Commit

Permalink
Merge pull request #2783 from cfromknecht/wtserver-db
Browse files Browse the repository at this point in the history
watchtower/wtdb: add bbolt-backed tower database
  • Loading branch information
Roasbeef committed Apr 27, 2019
2 parents a783f35 + 54c908b commit 0393793
Show file tree
Hide file tree
Showing 15 changed files with 2,043 additions and 150 deletions.
6 changes: 6 additions & 0 deletions channeldb/codec.go
Expand Up @@ -51,6 +51,12 @@ type UnknownElementType struct {
element interface{}
}

// NewUnknownElementType creates a new UnknownElementType error from the passed
// method name and element.
func NewUnknownElementType(method string, el interface{}) UnknownElementType {
return UnknownElementType{method: method, element: el}
}

// Error returns the name of the method that encountered the error, as well as
// the type that was unsupported.
func (e UnknownElementType) Error() string {
Expand Down
3 changes: 2 additions & 1 deletion watchtower/lookout/lookout_test.go
Expand Up @@ -15,6 +15,7 @@ import (
"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"
)

Expand Down Expand Up @@ -66,7 +67,7 @@ func makeAddrSlice(size int) []byte {
}

func TestLookoutBreachMatching(t *testing.T) {
db := wtdb.NewMockDB()
db := wtmock.NewTowerDB()

// Initialize an mock backend to feed the lookout blocks.
backend := lookout.NewMockBackend()
Expand Down
4 changes: 2 additions & 2 deletions watchtower/wtclient/client_test.go
Expand Up @@ -369,7 +369,7 @@ type testHarness struct {
clientDB *wtmock.ClientDB
clientCfg *wtclient.Config
client wtclient.Client
serverDB *wtdb.MockDB
serverDB *wtmock.TowerDB
serverCfg *wtserver.Config
server *wtserver.Server
net *mockNet
Expand Down Expand Up @@ -406,7 +406,7 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness {
}

const timeout = 200 * time.Millisecond
serverDB := wtdb.NewMockDB()
serverDB := wtmock.NewTowerDB()

serverCfg := &wtserver.Config{
DB: serverDB,
Expand Down
143 changes: 143 additions & 0 deletions watchtower/wtdb/codec.go
@@ -0,0 +1,143 @@
package wtdb

import (
"io"

"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtpolicy"
)

// UnknownElementType is an alias for channeldb.UnknownElementType.
type UnknownElementType = channeldb.UnknownElementType

// ReadElement deserializes a single element from the provided io.Reader.
func ReadElement(r io.Reader, element interface{}) error {
err := channeldb.ReadElement(r, element)
switch {

// Known to channeldb codec.
case err == nil:
return nil

// Fail if error is not UnknownElementType.
case err != nil:
if _, ok := err.(UnknownElementType); !ok {
return err
}
}

// Process any wtdb-specific extensions to the codec.
switch e := element.(type) {

case *SessionID:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}

case *BreachHint:
if _, err := io.ReadFull(r, e[:]); err != nil {
return err
}

case *wtpolicy.Policy:
var (
blobType uint16
sweepFeeRate uint64
)
err := channeldb.ReadElements(r,
&blobType,
&e.MaxUpdates,
&e.RewardBase,
&e.RewardRate,
&sweepFeeRate,
)
if err != nil {
return err
}

e.BlobType = blob.Type(blobType)
e.SweepFeeRate = lnwallet.SatPerKWeight(sweepFeeRate)

// Type is still unknown to wtdb extensions, fail.
default:
return channeldb.NewUnknownElementType(
"ReadElement", element,
)
}

return nil
}

// WriteElement serializes a single element into the provided io.Writer.
func WriteElement(w io.Writer, element interface{}) error {
err := channeldb.WriteElement(w, element)
switch {

// Known to channeldb codec.
case err == nil:
return nil

// Fail if error is not UnknownElementType.
case err != nil:
if _, ok := err.(UnknownElementType); !ok {
return err
}
}

// Process any wtdb-specific extensions to the codec.
switch e := element.(type) {

case SessionID:
if _, err := w.Write(e[:]); err != nil {
return err
}

case BreachHint:
if _, err := w.Write(e[:]); err != nil {
return err
}

case wtpolicy.Policy:
return channeldb.WriteElements(w,
uint16(e.BlobType),
e.MaxUpdates,
e.RewardBase,
e.RewardRate,
uint64(e.SweepFeeRate),
)

// Type is still unknown to wtdb extensions, fail.
default:
return channeldb.NewUnknownElementType(
"WriteElement", element,
)
}

return nil
}

// WriteElements serializes a variadic list of elements into the given
// io.Writer.
func WriteElements(w io.Writer, elements ...interface{}) error {
for _, element := range elements {
if err := WriteElement(w, element); err != nil {
return err
}
}

return nil
}

// ReadElements deserializes the provided io.Reader into a variadic list of
// target elements.
func ReadElements(r io.Reader, elements ...interface{}) error {
for _, element := range elements {
if err := ReadElement(r, element); err != nil {
return err
}
}

return nil
}
86 changes: 86 additions & 0 deletions watchtower/wtdb/codec_test.go
@@ -0,0 +1,86 @@
package wtdb_test

import (
"bytes"
"io"
"reflect"
"testing"
"testing/quick"

"github.com/lightningnetwork/lnd/watchtower/wtdb"
)

// dbObject is abstract object support encoding and decoding.
type dbObject interface {
Encode(io.Writer) error
Decode(io.Reader) error
}

// TestCodec serializes and deserializes wtdb objects in order to test that that
// the codec understands all of the required field types. The test also asserts
// that decoding an object into another results in an equivalent object.
func TestCodec(t *testing.T) {
mainScenario := func(obj dbObject) bool {
// Ensure encoding the object succeeds.
var b bytes.Buffer
err := obj.Encode(&b)
if err != nil {
t.Fatalf("unable to encode: %v", err)
return false
}

var obj2 dbObject
switch obj.(type) {
case *wtdb.SessionInfo:
obj2 = &wtdb.SessionInfo{}
case *wtdb.SessionStateUpdate:
obj2 = &wtdb.SessionStateUpdate{}
default:
t.Fatalf("unknown type: %T", obj)
return false
}

// Ensure decoding the object succeeds.
err = obj2.Decode(bytes.NewReader(b.Bytes()))
if err != nil {
t.Fatalf("unable to decode: %v", err)
return false
}

// Assert the original and decoded object match.
if !reflect.DeepEqual(obj, obj2) {
t.Fatalf("encode/decode mismatch, want: %v, "+
"got: %v", obj, obj2)
return false
}

return true
}

tests := []struct {
name string
scenario interface{}
}{
{
name: "SessionInfo",
scenario: func(obj wtdb.SessionInfo) bool {
return mainScenario(&obj)
},
},
{
name: "SessionStateUpdate",
scenario: func(obj wtdb.SessionStateUpdate) bool {
return mainScenario(&obj)
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if err := quick.Check(test.scenario, nil); err != nil {
t.Fatalf("fuzz checks for msg=%s failed: %v",
test.name, err)
}
})
}
}
45 changes: 45 additions & 0 deletions watchtower/wtdb/log.go
@@ -0,0 +1,45 @@
package wtdb

import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)

// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger

// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("WTDB", nil))
}

// DisableLog disables all library log output. Logging output is disabled
// by default until UseLogger is called.
func DisableLog() {
UseLogger(btclog.Disabled)
}

// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}

// logClosure is used to provide a closure over expensive logging operations so
// don't have to be performed when the logging level doesn't warrant it.
type logClosure func() string

// String invokes the underlying function and returns the result.
func (c logClosure) String() string {
return c()
}

// newLogClosure returns a new closure over a function that returns a string
// which itself provides a Stringer interface so that it can be used with the
// logging system.
func newLogClosure(c func() string) logClosure {
return logClosure(c)
}

0 comments on commit 0393793

Please sign in to comment.