Merge pull request #1512 from cfromknecht/wtwire
[watchtower/wtwire]: Watchtower Wire Messages
This commit is contained in:
commit
463d352fa8
78
watchtower/wtwire/create_session.go
Normal file
78
watchtower/wtwire/create_session.go
Normal file
@ -0,0 +1,78 @@
|
||||
package wtwire
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
|
||||
// CreateSession is sent from a client to tower when to negotiate a session, which
|
||||
// specifies the total number of updates that can be made, as well as fee rates.
|
||||
// An update is consumed by uploading an encrypted blob that contains
|
||||
// information required to sweep a revoked commitment transaction.
|
||||
type CreateSession struct {
|
||||
// BlobVersion specifies the blob format that must be used by all
|
||||
// updates sent under the session key used to negotiate this session.
|
||||
BlobVersion uint16
|
||||
|
||||
// MaxUpdates is the maximum number of updates the watchtower will honor
|
||||
// for this session.
|
||||
MaxUpdates uint16
|
||||
|
||||
// RewardRate is the fraction of the total balance of the revoked
|
||||
// commitment that the watchtower is entitled to. This value is
|
||||
// expressed in millionths of the total balance.
|
||||
RewardRate uint32
|
||||
|
||||
// SweepFeeRate expresses the intended fee rate to be used when
|
||||
// constructing the justice transaction. All sweep transactions created
|
||||
// for this session must use this value during construction, and the
|
||||
// signatures must implicitly commit to the resulting output values.
|
||||
SweepFeeRate lnwallet.SatPerKWeight
|
||||
}
|
||||
|
||||
// A compile time check to ensure CreateSession implements the wtwire.Message
|
||||
// interface.
|
||||
var _ Message = (*CreateSession)(nil)
|
||||
|
||||
// Decode deserializes a serialized CreateSession message stored in the passed
|
||||
// io.Reader observing the specified protocol version.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *CreateSession) Decode(r io.Reader, pver uint32) error {
|
||||
return ReadElements(r,
|
||||
&m.BlobVersion,
|
||||
&m.MaxUpdates,
|
||||
&m.RewardRate,
|
||||
&m.SweepFeeRate,
|
||||
)
|
||||
}
|
||||
|
||||
// Encode serializes the target CreateSession into the passed io.Writer
|
||||
// observing the protocol version specified.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *CreateSession) Encode(w io.Writer, pver uint32) error {
|
||||
return WriteElements(w,
|
||||
m.BlobVersion,
|
||||
m.MaxUpdates,
|
||||
m.RewardRate,
|
||||
m.SweepFeeRate,
|
||||
)
|
||||
}
|
||||
|
||||
// MsgType returns the integer uniquely identifying this message type on the
|
||||
// wire.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *CreateSession) MsgType() MessageType {
|
||||
return MsgCreateSession
|
||||
}
|
||||
|
||||
// MaxPayloadLength returns the maximum allowed payload size for a CreateSession
|
||||
// complete message observing the specified protocol version.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *CreateSession) MaxPayloadLength(uint32) uint32 {
|
||||
return 16
|
||||
}
|
91
watchtower/wtwire/create_session_reply.go
Normal file
91
watchtower/wtwire/create_session_reply.go
Normal file
@ -0,0 +1,91 @@
|
||||
package wtwire
|
||||
|
||||
import "io"
|
||||
|
||||
// CreateSessionCode is an error code returned by a watchtower in response to a
|
||||
// CreateSession message. The code directs the client in interpreting the payload
|
||||
// in the reply.
|
||||
type CreateSessionCode = ErrorCode
|
||||
|
||||
const (
|
||||
// CreateSessionCodeAlreadyExists is returned when a session is already
|
||||
// active for the public key used to connect to the watchtower. The
|
||||
// response includes the serialized reward address in case the original
|
||||
// reply was never received and/or processed by the client.
|
||||
CreateSessionCodeAlreadyExists CreateSessionCode = 60
|
||||
|
||||
// CreateSessionCodeRejectRejectMaxUpdates the tower rejected the maximum
|
||||
// number of state updates proposed by the client.
|
||||
CreateSessionCodeRejectRejectMaxUpdates CreateSessionCode = 61
|
||||
|
||||
// CreateSessionCodeRejectRewardRate the tower rejected the reward rate
|
||||
// proposed by the client.
|
||||
CreateSessionCodeRejectRewardRate CreateSessionCode = 62
|
||||
|
||||
// CreateSessionCodeRejectSweepFeeRate the tower rejected the sweep fee
|
||||
// rate proposed by the client.
|
||||
CreateSessionCodeRejectSweepFeeRate CreateSessionCode = 63
|
||||
)
|
||||
|
||||
// MaxCreateSessionReplyDataLength is the maximum size of the Data payload
|
||||
// returned in a CreateSessionReply message. This does not include the length of
|
||||
// the Data field, which is a varint up to 3 bytes in size.
|
||||
const MaxCreateSessionReplyDataLength = 1024
|
||||
|
||||
// CreateSessionReply is a message sent from watchtower to client in response to a
|
||||
// CreateSession message, and signals either an acceptance or rejection of the
|
||||
// proposed session parameters.
|
||||
type CreateSessionReply struct {
|
||||
// Code will be non-zero if the watchtower rejected the session init.
|
||||
Code CreateSessionCode
|
||||
|
||||
// Data is a byte slice returned the caller of the message, and is to be
|
||||
// interpreted according to the error Code. When the response is
|
||||
// CreateSessionCodeOK, data encodes the reward address to be included in
|
||||
// any sweep transactions if the reward is not dusty. Otherwise, it may
|
||||
// encode the watchtowers configured parameters for any policy
|
||||
// rejections.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// A compile time check to ensure CreateSessionReply implements the wtwire.Message
|
||||
// interface.
|
||||
var _ Message = (*CreateSessionReply)(nil)
|
||||
|
||||
// Decode deserializes a serialized CreateSessionReply message stored in the passed
|
||||
// io.Reader observing the specified protocol version.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *CreateSessionReply) Decode(r io.Reader, pver uint32) error {
|
||||
return ReadElements(r,
|
||||
&m.Code,
|
||||
&m.Data,
|
||||
)
|
||||
}
|
||||
|
||||
// Encode serializes the target CreateSessionReply into the passed io.Writer
|
||||
// observing the protocol version specified.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *CreateSessionReply) Encode(w io.Writer, pver uint32) error {
|
||||
return WriteElements(w,
|
||||
m.Code,
|
||||
m.Data,
|
||||
)
|
||||
}
|
||||
|
||||
// MsgType returns the integer uniquely identifying this message type on the
|
||||
// wire.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *CreateSessionReply) MsgType() MessageType {
|
||||
return MsgCreateSessionReply
|
||||
}
|
||||
|
||||
// MaxPayloadLength returns the maximum allowed payload size for a CreateSessionReply
|
||||
// complete message observing the specified protocol version.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *CreateSessionReply) MaxPayloadLength(uint32) uint32 {
|
||||
return 2 + 3 + MaxCreateSessionReplyDataLength
|
||||
}
|
62
watchtower/wtwire/error.go
Normal file
62
watchtower/wtwire/error.go
Normal file
@ -0,0 +1,62 @@
|
||||
package wtwire
|
||||
|
||||
import "io"
|
||||
|
||||
// Error is a generic error message that can be sent to a client if a request
|
||||
// fails outside of prescribed protocol errors. Typically this would be followed
|
||||
// by the server disconnecting the client, and so can be useful to transfering
|
||||
// the exact reason.
|
||||
type Error struct {
|
||||
// Code specifies the error code encountered by the server.
|
||||
Code ErrorCode
|
||||
|
||||
// Data encodes a payload whose contents can be interpreted by the
|
||||
// client in response to the error code.
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// NewError returns an freshly-initialized Error message.
|
||||
func NewError() *Error {
|
||||
return &Error{}
|
||||
}
|
||||
|
||||
// A compile time check to ensure Error implements the wtwire.Message interface.
|
||||
var _ Message = (*Error)(nil)
|
||||
|
||||
// Decode deserializes a serialized Error message stored in the passed io.Reader
|
||||
// observing the specified protocol version.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (e *Error) Decode(r io.Reader, pver uint32) error {
|
||||
return ReadElements(r,
|
||||
&e.Code,
|
||||
&e.Data,
|
||||
)
|
||||
}
|
||||
|
||||
// Encode serializes the target Error into the passed io.Writer observing the
|
||||
// protocol version specified.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (e *Error) Encode(w io.Writer, prver uint32) error {
|
||||
return WriteElements(w,
|
||||
e.Code,
|
||||
e.Data,
|
||||
)
|
||||
}
|
||||
|
||||
// MsgType returns the integer uniquely identifying this message type on the
|
||||
// wire.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (e *Error) MsgType() MessageType {
|
||||
return MsgError
|
||||
}
|
||||
|
||||
// MaxPayloadLength returns the maximum allowed payload size for a Error
|
||||
// complete message observing the specified protocol version.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (e *Error) MaxPayloadLength(uint32) uint32 {
|
||||
return MaxMessagePayload
|
||||
}
|
20
watchtower/wtwire/error_code.go
Normal file
20
watchtower/wtwire/error_code.go
Normal file
@ -0,0 +1,20 @@
|
||||
package wtwire
|
||||
|
||||
// ErrorCode represents a generic error code used when replying to watchtower
|
||||
// clients. Specific reply messages may extend the ErrorCode primitive and add
|
||||
// custom codes, so long as they don't collide with the generic error codes..
|
||||
type ErrorCode uint16
|
||||
|
||||
const (
|
||||
// CodeOK signals that the request was successfully processed by the
|
||||
// watchtower.
|
||||
CodeOK ErrorCode = 0
|
||||
|
||||
// CodeTemporaryFailure alerts the client that the watchtower is
|
||||
// temporarily unavailable, but that it may try again at a later time.
|
||||
CodeTemporaryFailure ErrorCode = 40
|
||||
|
||||
// CodePermanentFailure alerts the client that the watchtower has
|
||||
// permanently failed, and further communication should be avoided.
|
||||
CodePermanentFailure ErrorCode = 50
|
||||
)
|
26
watchtower/wtwire/features.go
Normal file
26
watchtower/wtwire/features.go
Normal file
@ -0,0 +1,26 @@
|
||||
package wtwire
|
||||
|
||||
import "github.com/lightningnetwork/lnd/lnwire"
|
||||
|
||||
// GlobalFeatures holds the globally advertised feature bits understood by
|
||||
// watchtower implementations.
|
||||
var GlobalFeatures map[lnwire.FeatureBit]string
|
||||
|
||||
// LocalFeatures holds the locally advertised feature bits understood by
|
||||
// watchtower implementations.
|
||||
var LocalFeatures = map[lnwire.FeatureBit]string{
|
||||
WtSessionsRequired: "wt-sessions-required",
|
||||
WtSessionsOptional: "wt-sessions-optional",
|
||||
}
|
||||
|
||||
const (
|
||||
// WtSessionsRequired specifies that the advertising node requires the
|
||||
// remote party to understand the protocol for creating and updating
|
||||
// watchtower sessions.
|
||||
WtSessionsRequired lnwire.FeatureBit = 8
|
||||
|
||||
// WtSessionsOptional specifies that the advertising node can support
|
||||
// a remote party who understand the protocol for creating and updating
|
||||
// watchtower sessions.
|
||||
WtSessionsOptional lnwire.FeatureBit = 9
|
||||
)
|
30
watchtower/wtwire/init.go
Normal file
30
watchtower/wtwire/init.go
Normal file
@ -0,0 +1,30 @@
|
||||
package wtwire
|
||||
|
||||
import "github.com/lightningnetwork/lnd/lnwire"
|
||||
|
||||
// Init is the first message sent over the watchtower wire protocol, and
|
||||
// specifies features and level of requiredness maintained by the sending node.
|
||||
// The watchtower Init message is identical to the LN Init message, except it
|
||||
// uses a different message type to ensure the two are not conflated.
|
||||
type Init struct {
|
||||
*lnwire.Init
|
||||
}
|
||||
|
||||
// NewInitMessage generates a new Init message from raw global and local feature
|
||||
// vectors.
|
||||
func NewInitMessage(gf, lf *lnwire.RawFeatureVector) *Init {
|
||||
return &Init{
|
||||
Init: lnwire.NewInitMessage(gf, lf),
|
||||
}
|
||||
}
|
||||
|
||||
// MsgType returns the integer uniquely identifying this message type on the
|
||||
// wire.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (msg *Init) MsgType() MessageType {
|
||||
return MsgInit
|
||||
}
|
||||
|
||||
// A compile-time constraint to ensure Init implements the Message interface.
|
||||
var _ Message = (*Init)(nil)
|
179
watchtower/wtwire/message.go
Normal file
179
watchtower/wtwire/message.go
Normal file
@ -0,0 +1,179 @@
|
||||
package wtwire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// MaxMessagePayload is the maximum bytes a message can be regardless of other
|
||||
// individual limits imposed by messages themselves.
|
||||
const MaxMessagePayload = 65535 // 65KB
|
||||
|
||||
// MessageType is the unique 2 byte big-endian integer that indicates the type
|
||||
// of message on the wire. All messages have a very simple header which
|
||||
// consists simply of 2-byte message type. We omit a length field, and checksum
|
||||
// as the Watchtower Protocol is intended to be encapsulated within a
|
||||
// confidential+authenticated cryptographic messaging protocol.
|
||||
type MessageType uint16
|
||||
|
||||
// The currently defined message types within this current version of the
|
||||
// Watchtower protocol.
|
||||
const (
|
||||
// MsgInit identifies an encoded Init message.
|
||||
MsgInit MessageType = 300
|
||||
|
||||
// MsgError identifies an encoded Error message.
|
||||
MsgError = 301
|
||||
|
||||
// MsgCreateSession identifies an encoded CreateSession message.
|
||||
MsgCreateSession MessageType = 302
|
||||
|
||||
// MsgCreateSessionReply identifies an encoded CreateSessionReply message.
|
||||
MsgCreateSessionReply MessageType = 303
|
||||
|
||||
// MsgStateUpdate identifies an encoded StateUpdate message.
|
||||
MsgStateUpdate MessageType = 304
|
||||
|
||||
// MsgStateUpdateReply identifies an encoded StateUpdateReply message.
|
||||
MsgStateUpdateReply MessageType = 305
|
||||
)
|
||||
|
||||
// String returns a human readable description of the message type.
|
||||
func (m MessageType) String() string {
|
||||
switch m {
|
||||
case MsgInit:
|
||||
return "Init"
|
||||
case MsgCreateSession:
|
||||
return "MsgCreateSession"
|
||||
case MsgCreateSessionReply:
|
||||
return "MsgCreateSessionReply"
|
||||
case MsgStateUpdate:
|
||||
return "MsgStateUpdate"
|
||||
case MsgStateUpdateReply:
|
||||
return "MsgStateUpdateReply"
|
||||
case MsgError:
|
||||
return "Error"
|
||||
default:
|
||||
return "<unknown>"
|
||||
}
|
||||
}
|
||||
|
||||
// Serializable is an interface which defines a lightning wire serializable
|
||||
// object.
|
||||
type Serializable = lnwire.Serializable
|
||||
|
||||
// Message is an interface that defines a lightning wire protocol message. The
|
||||
// interface is general in order to allow implementing types full control over
|
||||
// the representation of its data.
|
||||
type Message interface {
|
||||
Serializable
|
||||
|
||||
// MsgType returns a MessageType that uniquely identifies the message to
|
||||
// be encoded.
|
||||
MsgType() MessageType
|
||||
|
||||
// MaxMessagePayload is the maximum serialized length that a particular
|
||||
// message type can take.
|
||||
MaxPayloadLength(uint32) uint32
|
||||
}
|
||||
|
||||
// makeEmptyMessage creates a new empty message of the proper concrete type
|
||||
// based on the passed message type.
|
||||
func makeEmptyMessage(msgType MessageType) (Message, error) {
|
||||
var msg Message
|
||||
|
||||
switch msgType {
|
||||
case MsgInit:
|
||||
msg = &Init{&lnwire.Init{}}
|
||||
case MsgCreateSession:
|
||||
msg = &CreateSession{}
|
||||
case MsgCreateSessionReply:
|
||||
msg = &CreateSessionReply{}
|
||||
case MsgStateUpdate:
|
||||
msg = &StateUpdate{}
|
||||
case MsgStateUpdateReply:
|
||||
msg = &StateUpdateReply{}
|
||||
case MsgError:
|
||||
msg = &Error{}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown message type [%d]", msgType)
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// WriteMessage writes a lightning Message to w including the necessary header
|
||||
// information and returns the number of bytes written.
|
||||
func WriteMessage(w io.Writer, msg Message, pver uint32) (int, error) {
|
||||
totalBytes := 0
|
||||
|
||||
// Encode the message payload itself into a temporary buffer.
|
||||
// TODO(roasbeef): create buffer pool
|
||||
var bw bytes.Buffer
|
||||
if err := msg.Encode(&bw, pver); err != nil {
|
||||
return totalBytes, err
|
||||
}
|
||||
payload := bw.Bytes()
|
||||
lenp := len(payload)
|
||||
|
||||
// Enforce maximum overall message payload.
|
||||
if lenp > MaxMessagePayload {
|
||||
return totalBytes, fmt.Errorf("message payload is too large - "+
|
||||
"encoded %d bytes, but maximum message payload is %d bytes",
|
||||
lenp, MaxMessagePayload)
|
||||
}
|
||||
|
||||
// Enforce maximum message payload on the message type.
|
||||
mpl := msg.MaxPayloadLength(pver)
|
||||
if uint32(lenp) > mpl {
|
||||
return totalBytes, fmt.Errorf("message payload is too large - "+
|
||||
"encoded %d bytes, but maximum message payload of "+
|
||||
"type %v is %d bytes", lenp, msg.MsgType(), mpl)
|
||||
}
|
||||
|
||||
// With the initial sanity checks complete, we'll now write out the
|
||||
// message type itself.
|
||||
var mType [2]byte
|
||||
binary.BigEndian.PutUint16(mType[:], uint16(msg.MsgType()))
|
||||
n, err := w.Write(mType[:])
|
||||
totalBytes += n
|
||||
if err != nil {
|
||||
return totalBytes, err
|
||||
}
|
||||
|
||||
// With the message type written, we'll now write out the raw payload
|
||||
// itself.
|
||||
n, err = w.Write(payload)
|
||||
totalBytes += n
|
||||
|
||||
return totalBytes, err
|
||||
}
|
||||
|
||||
// ReadMessage reads, validates, and parses the next Watchtower message from r
|
||||
// for the provided protocol version.
|
||||
func ReadMessage(r io.Reader, pver uint32) (Message, error) {
|
||||
// First, we'll read out the first two bytes of the message so we can
|
||||
// create the proper empty message.
|
||||
var mType [2]byte
|
||||
if _, err := io.ReadFull(r, mType[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgType := MessageType(binary.BigEndian.Uint16(mType[:]))
|
||||
|
||||
// Now that we know the target message type, we can create the proper
|
||||
// empty message type and decode the message into it.
|
||||
msg, err := makeEmptyMessage(msgType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := msg.Decode(r, pver); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
88
watchtower/wtwire/state_update.go
Normal file
88
watchtower/wtwire/state_update.go
Normal file
@ -0,0 +1,88 @@
|
||||
package wtwire
|
||||
|
||||
import "io"
|
||||
|
||||
// StateUpdate transmits an encrypted state update from the client to the
|
||||
// watchtower. Each state update is tied to particular session, identified by
|
||||
// the client's brontine key used to make the request.
|
||||
type StateUpdate struct {
|
||||
// SeqNum is a 1-indexed, monotonically incrementing sequence number.
|
||||
// This number represents to the client's expected sequence number when
|
||||
// sending updates sent to the watchtower. This value must always be
|
||||
// less or equal than the negotiated MaxUpdates for the session, and
|
||||
// greater than the LastApplied sent in the same message.
|
||||
SeqNum uint16
|
||||
|
||||
// LastApplied echos the LastApplied value returned from watchtower,
|
||||
// allowing the tower to detect faulty clients. This allow provides a
|
||||
// feedback mechanism for the tower if updates are allowed to stream in
|
||||
// an async fashion.
|
||||
LastApplied uint16
|
||||
|
||||
// IsComplete is 1 if the watchtower should close the connection after
|
||||
// responding, and 0 otherwise.
|
||||
IsComplete uint8
|
||||
|
||||
// Hint is the 16-byte prefix of the revoked commitment transaction ID
|
||||
// for which the encrypted blob can exact justice.
|
||||
Hint [16]byte
|
||||
|
||||
// EncryptedBlob is the serialized ciphertext containing all necessary
|
||||
// information to sweep the commitment transaction corresponding to the
|
||||
// Hint. The ciphertext is to be encrypted using the full transaction ID
|
||||
// of the revoked commitment transaction.
|
||||
//
|
||||
// The plaintext MUST be encoded using the negotiated Version for
|
||||
// this session. In addition, the signatures must be computed over a
|
||||
// sweep transaction honoring the decided SweepFeeRate, RewardRate, and
|
||||
// (possibly) reward address returned in the SessionInitReply.
|
||||
EncryptedBlob []byte
|
||||
}
|
||||
|
||||
// A compile time check to ensure StateUpdate implements the wtwire.Message
|
||||
// interface.
|
||||
var _ Message = (*StateUpdate)(nil)
|
||||
|
||||
// Decode deserializes a serialized StateUpdate message stored in the passed
|
||||
// io.Reader observing the specified protocol version.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *StateUpdate) Decode(r io.Reader, pver uint32) error {
|
||||
return ReadElements(r,
|
||||
&m.SeqNum,
|
||||
&m.LastApplied,
|
||||
&m.IsComplete,
|
||||
&m.Hint,
|
||||
&m.EncryptedBlob,
|
||||
)
|
||||
}
|
||||
|
||||
// Encode serializes the target StateUpdate into the passed io.Writer
|
||||
// observing the protocol version specified.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *StateUpdate) Encode(w io.Writer, pver uint32) error {
|
||||
return WriteElements(w,
|
||||
m.SeqNum,
|
||||
m.LastApplied,
|
||||
m.IsComplete,
|
||||
m.Hint,
|
||||
m.EncryptedBlob,
|
||||
)
|
||||
}
|
||||
|
||||
// MsgType returns the integer uniquely identifying this message type on the
|
||||
// wire.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *StateUpdate) MsgType() MessageType {
|
||||
return MsgStateUpdate
|
||||
}
|
||||
|
||||
// MaxPayloadLength returns the maximum allowed payload size for a StateUpdate
|
||||
// complete message observing the specified protocol version.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (m *StateUpdate) MaxPayloadLength(uint32) uint32 {
|
||||
return MaxMessagePayload
|
||||
}
|
86
watchtower/wtwire/state_update_reply.go
Normal file
86
watchtower/wtwire/state_update_reply.go
Normal file
@ -0,0 +1,86 @@
|
||||
package wtwire
|
||||
|
||||
import "io"
|
||||
|
||||
// StateUpdateCode is an error code returned by a watchtower in response to a
|
||||
// StateUpdate message.
|
||||
type StateUpdateCode = ErrorCode
|
||||
|
||||
const (
|
||||
// StateUpdateCodeClientBehind signals that the client's sequence number
|
||||
// is behind what the watchtower expects based on its LastApplied. This
|
||||
// error should cause the client to record the LastApplied field in the
|
||||
// response, and initiate another attempt with the proper sequence
|
||||
// number.
|
||||
//
|
||||
// NOTE: Repeated occurrences of this could be interpreted as an attempt
|
||||
// to siphon state updates from the client. If the client believes it
|
||||
// is not violating the protocol, this could be grounds to blacklist
|
||||
// this tower from future session negotiation.
|
||||
StateUpdateCodeClientBehind StateUpdateCode = 70
|
||||
|
||||
// StateUpdateCodeMaxUpdatesExceeded signals that the client tried to
|
||||
// send a sequence number beyond the negotiated MaxUpdates of the
|
||||
// session.
|
||||
StateUpdateCodeMaxUpdatesExceeded StateUpdateCode = 71
|
||||
|
||||
// StateUpdateCodeSeqNumOutOfOrder signals the client sent an update
|
||||
// that does not follow the required incremental monotonicity required
|
||||
// by the tower.
|
||||
StateUpdateCodeSeqNumOutOfOrder StateUpdateCode = 72
|
||||
)
|
||||
|
||||
// StateUpdateReply is a message sent from watchtower to client in response to a
|
||||
// StateUpdate message, and signals either an acceptance or rejection of the
|
||||
// proposed state update.
|
||||
type StateUpdateReply struct {
|
||||
// Code will be non-zero if the watchtower rejected the state update.
|
||||
Code StateUpdateCode
|
||||
|
||||
// LastApplied returns the sequence number of the last accepted update
|
||||
// known to the watchtower. If the update was successful, this value
|
||||
// should be the sequence number of the last update sent.
|
||||
LastApplied uint16
|
||||
}
|
||||
|
||||
// A compile time check to ensure StateUpdateReply implements the wtwire.Message
|
||||
// interface.
|
||||
var _ Message = (*StateUpdateReply)(nil)
|
||||
|
||||
// Decode deserializes a serialized StateUpdateReply message stored in the passed
|
||||
// io.Reader observing the specified protocol version.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (t *StateUpdateReply) Decode(r io.Reader, pver uint32) error {
|
||||
return ReadElements(r,
|
||||
&t.Code,
|
||||
&t.LastApplied,
|
||||
)
|
||||
}
|
||||
|
||||
// Encode serializes the target StateUpdateReply into the passed io.Writer
|
||||
// observing the protocol version specified.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (t *StateUpdateReply) Encode(w io.Writer, pver uint32) error {
|
||||
return WriteElements(w,
|
||||
t.Code,
|
||||
t.LastApplied,
|
||||
)
|
||||
}
|
||||
|
||||
// MsgType returns the integer uniquely identifying this message type on the
|
||||
// wire.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (t *StateUpdateReply) MsgType() MessageType {
|
||||
return MsgStateUpdateReply
|
||||
}
|
||||
|
||||
// MaxPayloadLength returns the maximum allowed payload size for a
|
||||
// StateUpdateReply complete message observing the specified protocol version.
|
||||
//
|
||||
// This is part of the wtwire.Message interface.
|
||||
func (t *StateUpdateReply) MaxPayloadLength(uint32) uint32 {
|
||||
return 4
|
||||
}
|
210
watchtower/wtwire/wtwire.go
Normal file
210
watchtower/wtwire/wtwire.go
Normal file
@ -0,0 +1,210 @@
|
||||
package wtwire
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
)
|
||||
|
||||
// WriteElement is a one-stop shop to write the big endian representation of
|
||||
// any element which is to be serialized for the wire protocol. The passed
|
||||
// io.Writer should be backed by an appropriately sized byte slice, or be able
|
||||
// to dynamically expand to accommodate additional data.
|
||||
func WriteElement(w io.Writer, element interface{}) error {
|
||||
switch e := element.(type) {
|
||||
case uint8:
|
||||
var b [1]byte
|
||||
b[0] = e
|
||||
if _, err := w.Write(b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case uint16:
|
||||
var b [2]byte
|
||||
binary.BigEndian.PutUint16(b[:], e)
|
||||
if _, err := w.Write(b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case uint32:
|
||||
var b [4]byte
|
||||
binary.BigEndian.PutUint32(b[:], e)
|
||||
if _, err := w.Write(b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case uint64:
|
||||
var b [8]byte
|
||||
binary.BigEndian.PutUint64(b[:], e)
|
||||
if _, err := w.Write(b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case [16]byte:
|
||||
if _, err := w.Write(e[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case [32]byte:
|
||||
if _, err := w.Write(e[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case [33]byte:
|
||||
if _, err := w.Write(e[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case []byte:
|
||||
if err := wire.WriteVarBytes(w, 0, e); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case lnwallet.SatPerKWeight:
|
||||
var b [8]byte
|
||||
binary.BigEndian.PutUint64(b[:], uint64(e))
|
||||
if _, err := w.Write(b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case ErrorCode:
|
||||
var b [2]byte
|
||||
binary.BigEndian.PutUint16(b[:], uint16(e))
|
||||
if _, err := w.Write(b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *btcec.PublicKey:
|
||||
if e == nil {
|
||||
return fmt.Errorf("cannot write nil pubkey")
|
||||
}
|
||||
|
||||
var b [33]byte
|
||||
serializedPubkey := e.SerializeCompressed()
|
||||
copy(b[:], serializedPubkey)
|
||||
if _, err := w.Write(b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Unknown type in WriteElement: %T", e)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteElements is writes each element in the elements slice to the passed
|
||||
// io.Writer using WriteElement.
|
||||
func WriteElements(w io.Writer, elements ...interface{}) error {
|
||||
for _, element := range elements {
|
||||
err := WriteElement(w, element)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadElement is a one-stop utility function to deserialize any datastructure
|
||||
// encoded using the serialization format of lnwire.
|
||||
func ReadElement(r io.Reader, element interface{}) error {
|
||||
switch e := element.(type) {
|
||||
case *uint8:
|
||||
var b [1]uint8
|
||||
if _, err := r.Read(b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = b[0]
|
||||
|
||||
case *uint16:
|
||||
var b [2]byte
|
||||
if _, err := io.ReadFull(r, b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = binary.BigEndian.Uint16(b[:])
|
||||
|
||||
case *uint32:
|
||||
var b [4]byte
|
||||
if _, err := io.ReadFull(r, b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = binary.BigEndian.Uint32(b[:])
|
||||
|
||||
case *uint64:
|
||||
var b [8]byte
|
||||
if _, err := io.ReadFull(r, b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = binary.BigEndian.Uint64(b[:])
|
||||
|
||||
case *[16]byte:
|
||||
if _, err := io.ReadFull(r, e[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *[32]byte:
|
||||
if _, err := io.ReadFull(r, e[:]); err != nil {
|
||||
|
||||
}
|
||||
|
||||
case *[33]byte:
|
||||
if _, err := io.ReadFull(r, e[:]); err != nil {
|
||||
|
||||
}
|
||||
|
||||
case *[]byte:
|
||||
bytes, err := wire.ReadVarBytes(r, 0, 66000, "[]byte")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*e = bytes
|
||||
|
||||
case *lnwallet.SatPerKWeight:
|
||||
var b [8]byte
|
||||
if _, err := io.ReadFull(r, b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = lnwallet.SatPerKWeight(binary.BigEndian.Uint64(b[:]))
|
||||
|
||||
case *ErrorCode:
|
||||
var b [2]byte
|
||||
if _, err := io.ReadFull(r, b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
*e = ErrorCode(binary.BigEndian.Uint16(b[:]))
|
||||
|
||||
case **btcec.PublicKey:
|
||||
var b [btcec.PubKeyBytesLenCompressed]byte
|
||||
if _, err := io.ReadFull(r, b[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pubKey, err := btcec.ParsePubKey(b[:], btcec.S256())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*e = pubKey
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Unknown type in ReadElement: %T", e)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadElements deserializes a variable number of elements into the passed
|
||||
// io.Reader, with each element being deserialized according to the ReadElement
|
||||
// function.
|
||||
func ReadElements(r io.Reader, elements ...interface{}) error {
|
||||
for _, element := range elements {
|
||||
err := ReadElement(r, element)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
152
watchtower/wtwire/wtwire_test.go
Normal file
152
watchtower/wtwire/wtwire_test.go
Normal file
@ -0,0 +1,152 @@
|
||||
package wtwire_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/watchtower/wtwire"
|
||||
)
|
||||
|
||||
func randRawFeatureVector(r *rand.Rand) *lnwire.RawFeatureVector {
|
||||
featureVec := lnwire.NewRawFeatureVector()
|
||||
for i := 0; i < 10000; i++ {
|
||||
if r.Int31n(2) == 0 {
|
||||
featureVec.Set(lnwire.FeatureBit(i))
|
||||
}
|
||||
}
|
||||
return featureVec
|
||||
}
|
||||
|
||||
// TestWatchtowerWireProtocol uses the testing/quick package to create a series
|
||||
// of fuzz tests to attempt to break a primary scenario which is implemented as
|
||||
// property based testing scenario.
|
||||
func TestWatchtowerWireProtocol(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// mainScenario is the primary test that will programmatically be
|
||||
// executed for all registered wire messages. The quick-checker within
|
||||
// testing/quick will attempt to find an input to this function, s.t
|
||||
// the function returns false, if so then we've found an input that
|
||||
// violates our model of the system.
|
||||
mainScenario := func(msg wtwire.Message) bool {
|
||||
// Give a new message, we'll serialize the message into a new
|
||||
// bytes buffer.
|
||||
var b bytes.Buffer
|
||||
if _, err := wtwire.WriteMessage(&b, msg, 0); err != nil {
|
||||
t.Fatalf("unable to write msg: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Next, we'll ensure that the serialized payload (subtracting
|
||||
// the 2 bytes for the message type) is _below_ the specified
|
||||
// max payload size for this message.
|
||||
payloadLen := uint32(b.Len()) - 2
|
||||
if payloadLen > msg.MaxPayloadLength(0) {
|
||||
t.Fatalf("msg payload constraint violated: %v > %v",
|
||||
payloadLen, msg.MaxPayloadLength(0))
|
||||
return false
|
||||
}
|
||||
|
||||
// Finally, we'll deserialize the message from the written
|
||||
// buffer, and finally assert that the messages are equal.
|
||||
newMsg, err := wtwire.ReadMessage(&b, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read msg: %v", err)
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(msg, newMsg) {
|
||||
t.Fatalf("messages don't match after re-encoding: %v "+
|
||||
"vs %v", spew.Sdump(msg), spew.Sdump(newMsg))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
customTypeGen := map[wtwire.MessageType]func([]reflect.Value, *rand.Rand){
|
||||
wtwire.MsgInit: func(v []reflect.Value, r *rand.Rand) {
|
||||
req := wtwire.NewInitMessage(
|
||||
randRawFeatureVector(r),
|
||||
randRawFeatureVector(r),
|
||||
)
|
||||
|
||||
v[0] = reflect.ValueOf(*req)
|
||||
},
|
||||
}
|
||||
|
||||
// With the above types defined, we'll now generate a slice of
|
||||
// scenarios to feed into quick.Check. The function scans in input
|
||||
// space of the target function under test, so we'll need to create a
|
||||
// series of wrapper functions to force it to iterate over the target
|
||||
// types, but re-use the mainScenario defined above.
|
||||
tests := []struct {
|
||||
msgType wtwire.MessageType
|
||||
scenario interface{}
|
||||
}{
|
||||
{
|
||||
msgType: wtwire.MsgInit,
|
||||
scenario: func(m wtwire.Init) bool {
|
||||
return mainScenario(&m)
|
||||
},
|
||||
},
|
||||
{
|
||||
msgType: wtwire.MsgCreateSession,
|
||||
scenario: func(m wtwire.CreateSession) bool {
|
||||
return mainScenario(&m)
|
||||
},
|
||||
},
|
||||
{
|
||||
msgType: wtwire.MsgCreateSessionReply,
|
||||
scenario: func(m wtwire.CreateSessionReply) bool {
|
||||
return mainScenario(&m)
|
||||
},
|
||||
},
|
||||
{
|
||||
msgType: wtwire.MsgStateUpdate,
|
||||
scenario: func(m wtwire.StateUpdate) bool {
|
||||
return mainScenario(&m)
|
||||
},
|
||||
},
|
||||
{
|
||||
msgType: wtwire.MsgStateUpdateReply,
|
||||
scenario: func(m wtwire.StateUpdateReply) bool {
|
||||
return mainScenario(&m)
|
||||
},
|
||||
},
|
||||
{
|
||||
msgType: wtwire.MsgError,
|
||||
scenario: func(m wtwire.Error) bool {
|
||||
return mainScenario(&m)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
var config *quick.Config
|
||||
|
||||
// If the type defined is within the custom type gen map above,
|
||||
// the we'll modify the default config to use this Value
|
||||
// function that knows how to generate the proper types.
|
||||
if valueGen, ok := customTypeGen[test.msgType]; ok {
|
||||
config = &quick.Config{
|
||||
Values: valueGen,
|
||||
}
|
||||
}
|
||||
|
||||
t.Logf("Running fuzz tests for msgType=%v", test.msgType)
|
||||
if err := quick.Check(test.scenario, config); err != nil {
|
||||
t.Fatalf("fuzz checks for msg=%v failed: %v",
|
||||
test.msgType, err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
Loading…
Reference in New Issue
Block a user