lnwire: Add upfront shutdown messages and feature bit
This commit adds the feature bit and additional fields required in `open_channel` and `accept_channel` wire messages for `option_upfront_shutdown_script`.
This commit is contained in:
parent
db21c394b2
commit
2fb7172725
@ -64,3 +64,10 @@ linters:
|
|||||||
issues:
|
issues:
|
||||||
# Only show newly introduced problems.
|
# Only show newly introduced problems.
|
||||||
new-from-rev: 01f696afce2f9c0d4ed854edefa3846891d01d8a
|
new-from-rev: 01f696afce2f9c0d4ed854edefa3846891d01d8a
|
||||||
|
|
||||||
|
exclude-rules:
|
||||||
|
# Exclude gosec from running for tests so that tests with weak randomness
|
||||||
|
# (math/rand) will pass the linter.
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
@ -86,6 +86,12 @@ type AcceptChannel struct {
|
|||||||
// base point in order to derive the revocation keys that are placed
|
// base point in order to derive the revocation keys that are placed
|
||||||
// within the commitment transaction of the sender.
|
// within the commitment transaction of the sender.
|
||||||
FirstCommitmentPoint *btcec.PublicKey
|
FirstCommitmentPoint *btcec.PublicKey
|
||||||
|
|
||||||
|
// UpfrontShutdownScript is the script to which the channel funds should
|
||||||
|
// be paid when mutually closing the channel. This field is optional, and
|
||||||
|
// and has a length prefix, so a zero will be written if it is not set
|
||||||
|
// and its length followed by the script will be written if it is set.
|
||||||
|
UpfrontShutdownScript DeliveryAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// A compile time check to ensure AcceptChannel implements the lnwire.Message
|
// A compile time check to ensure AcceptChannel implements the lnwire.Message
|
||||||
@ -113,6 +119,7 @@ func (a *AcceptChannel) Encode(w io.Writer, pver uint32) error {
|
|||||||
a.DelayedPaymentPoint,
|
a.DelayedPaymentPoint,
|
||||||
a.HtlcPoint,
|
a.HtlcPoint,
|
||||||
a.FirstCommitmentPoint,
|
a.FirstCommitmentPoint,
|
||||||
|
a.UpfrontShutdownScript,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +129,8 @@ func (a *AcceptChannel) Encode(w io.Writer, pver uint32) error {
|
|||||||
//
|
//
|
||||||
// This is part of the lnwire.Message interface.
|
// This is part of the lnwire.Message interface.
|
||||||
func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error {
|
func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error {
|
||||||
return ReadElements(r,
|
// Read all the mandatory fields in the accept message.
|
||||||
|
err := ReadElements(r,
|
||||||
a.PendingChannelID[:],
|
a.PendingChannelID[:],
|
||||||
&a.DustLimit,
|
&a.DustLimit,
|
||||||
&a.MaxValueInFlight,
|
&a.MaxValueInFlight,
|
||||||
@ -138,6 +146,17 @@ func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error {
|
|||||||
&a.HtlcPoint,
|
&a.HtlcPoint,
|
||||||
&a.FirstCommitmentPoint,
|
&a.FirstCommitmentPoint,
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the optional upfront shutdown script field. If it is not there,
|
||||||
|
// silence the EOF error.
|
||||||
|
err = ReadElement(r, &a.UpfrontShutdownScript)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MsgType returns the MessageType code which uniquely identifies this message
|
// MsgType returns the MessageType code which uniquely identifies this message
|
||||||
@ -154,5 +173,10 @@ func (a *AcceptChannel) MsgType() MessageType {
|
|||||||
// This is part of the lnwire.Message interface.
|
// This is part of the lnwire.Message interface.
|
||||||
func (a *AcceptChannel) MaxPayloadLength(uint32) uint32 {
|
func (a *AcceptChannel) MaxPayloadLength(uint32) uint32 {
|
||||||
// 32 + (8 * 4) + (4 * 1) + (2 * 2) + (33 * 6)
|
// 32 + (8 * 4) + (4 * 1) + (2 * 2) + (33 * 6)
|
||||||
return 270
|
var length uint32 = 270 // base length
|
||||||
|
|
||||||
|
// Upfront shutdown script max length.
|
||||||
|
length += 2 + deliveryAddressMaxSize
|
||||||
|
|
||||||
|
return length
|
||||||
}
|
}
|
||||||
|
71
lnwire/accept_channel_test.go
Normal file
71
lnwire/accept_channel_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package lnwire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestDecodeAcceptChannel tests decoding of an accept channel wire message with
|
||||||
|
// and without the optional upfront shutdown script.
|
||||||
|
func TestDecodeAcceptChannel(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
shutdownScript DeliveryAddress
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no upfront shutdown script",
|
||||||
|
shutdownScript: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty byte array",
|
||||||
|
shutdownScript: []byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "upfront shutdown script set",
|
||||||
|
shutdownScript: []byte("example"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
priv, err := btcec.NewPrivateKey(btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot create privkey: %v", err)
|
||||||
|
}
|
||||||
|
pk := priv.PubKey()
|
||||||
|
|
||||||
|
encoded := &AcceptChannel{
|
||||||
|
PendingChannelID: [32]byte{},
|
||||||
|
FundingKey: pk,
|
||||||
|
RevocationPoint: pk,
|
||||||
|
PaymentPoint: pk,
|
||||||
|
DelayedPaymentPoint: pk,
|
||||||
|
HtlcPoint: pk,
|
||||||
|
FirstCommitmentPoint: pk,
|
||||||
|
UpfrontShutdownScript: test.shutdownScript,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if _, err := WriteMessage(buf, encoded, 0); err != nil {
|
||||||
|
t.Fatalf("cannot write message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := ReadMessage(buf, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cannot read message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded := msg.(*AcceptChannel)
|
||||||
|
if !bytes.Equal(
|
||||||
|
decoded.UpfrontShutdownScript, encoded.UpfrontShutdownScript,
|
||||||
|
) {
|
||||||
|
t.Fatalf("decoded script: %x does not equal encoded script: %x",
|
||||||
|
decoded.UpfrontShutdownScript, encoded.UpfrontShutdownScript)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -42,6 +42,16 @@ const (
|
|||||||
// connection is established.
|
// connection is established.
|
||||||
InitialRoutingSync FeatureBit = 3
|
InitialRoutingSync FeatureBit = 3
|
||||||
|
|
||||||
|
// UpfrontShutdownScriptRequired is a feature bit which indicates that a
|
||||||
|
// peer *requires* that the remote peer accept an upfront shutdown script to
|
||||||
|
// which payout is enforced on cooperative closes.
|
||||||
|
UpfrontShutdownScriptRequired FeatureBit = 4
|
||||||
|
|
||||||
|
// UpfrontShutdownScriptOptional is an optional feature bit which indicates
|
||||||
|
// that the peer will accept an upfront shutdown script to which payout is
|
||||||
|
// enforced on cooperative closes.
|
||||||
|
UpfrontShutdownScriptOptional FeatureBit = 5
|
||||||
|
|
||||||
// GossipQueriesRequired is a feature bit that indicates that the
|
// GossipQueriesRequired is a feature bit that indicates that the
|
||||||
// receiving peer MUST know of the set of features that allows nodes to
|
// receiving peer MUST know of the set of features that allows nodes to
|
||||||
// more efficiently query the network view of peers on the network for
|
// more efficiently query the network view of peers on the network for
|
||||||
@ -89,15 +99,17 @@ const (
|
|||||||
// feature bits must be assigned a name in this mapping, and feature bit pairs
|
// feature bits must be assigned a name in this mapping, and feature bit pairs
|
||||||
// must be assigned together for correct behavior.
|
// must be assigned together for correct behavior.
|
||||||
var Features = map[FeatureBit]string{
|
var Features = map[FeatureBit]string{
|
||||||
DataLossProtectRequired: "data-loss-protect",
|
DataLossProtectRequired: "data-loss-protect",
|
||||||
DataLossProtectOptional: "data-loss-protect",
|
DataLossProtectOptional: "data-loss-protect",
|
||||||
InitialRoutingSync: "initial-routing-sync",
|
InitialRoutingSync: "initial-routing-sync",
|
||||||
GossipQueriesRequired: "gossip-queries",
|
UpfrontShutdownScriptRequired: "upfront-shutdown-script",
|
||||||
GossipQueriesOptional: "gossip-queries",
|
UpfrontShutdownScriptOptional: "upfront-shutdown-script",
|
||||||
TLVOnionPayloadRequired: "tlv-onion",
|
GossipQueriesRequired: "gossip-queries",
|
||||||
TLVOnionPayloadOptional: "tlv-onion",
|
GossipQueriesOptional: "gossip-queries",
|
||||||
StaticRemoteKeyOptional: "static-remote-key",
|
TLVOnionPayloadRequired: "tlv-onion",
|
||||||
StaticRemoteKeyRequired: "static-remote-key",
|
TLVOnionPayloadOptional: "tlv-onion",
|
||||||
|
StaticRemoteKeyOptional: "static-remote-key",
|
||||||
|
StaticRemoteKeyRequired: "static-remote-key",
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A
|
// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A
|
||||||
|
@ -816,8 +816,8 @@ func ReadElement(r io.Reader, element interface{}) error {
|
|||||||
}
|
}
|
||||||
length := binary.BigEndian.Uint16(addrLen[:])
|
length := binary.BigEndian.Uint16(addrLen[:])
|
||||||
|
|
||||||
var addrBytes [34]byte
|
var addrBytes [deliveryAddressMaxSize]byte
|
||||||
if length > 34 {
|
if length > deliveryAddressMaxSize {
|
||||||
return fmt.Errorf("Cannot read %d bytes into addrBytes", length)
|
return fmt.Errorf("Cannot read %d bytes into addrBytes", length)
|
||||||
}
|
}
|
||||||
if _, err = io.ReadFull(r, addrBytes[:length]); err != nil {
|
if _, err = io.ReadFull(r, addrBytes[:length]); err != nil {
|
||||||
|
@ -67,6 +67,15 @@ func randRawKey() ([33]byte, error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func randDeliveryAddress(r *rand.Rand) (DeliveryAddress, error) {
|
||||||
|
// Generate size minimum one. Empty scripts should be tested specifically.
|
||||||
|
size := r.Intn(deliveryAddressMaxSize) + 1
|
||||||
|
da := DeliveryAddress(make([]byte, size))
|
||||||
|
|
||||||
|
_, err := r.Read(da)
|
||||||
|
return da, err
|
||||||
|
}
|
||||||
|
|
||||||
func randRawFeatureVector(r *rand.Rand) *RawFeatureVector {
|
func randRawFeatureVector(r *rand.Rand) *RawFeatureVector {
|
||||||
featureVec := NewRawFeatureVector()
|
featureVec := NewRawFeatureVector()
|
||||||
for i := 0; i < 10000; i++ {
|
for i := 0; i < 10000; i++ {
|
||||||
@ -241,6 +250,9 @@ func TestEmptyMessageUnknownType(t *testing.T) {
|
|||||||
// TestLightningWireProtocol uses the testing/quick package to create a series
|
// TestLightningWireProtocol uses the testing/quick package to create a series
|
||||||
// of fuzz tests to attempt to break a primary scenario which is implemented as
|
// of fuzz tests to attempt to break a primary scenario which is implemented as
|
||||||
// property based testing scenario.
|
// property based testing scenario.
|
||||||
|
//
|
||||||
|
// Debug help: when the message payload can reach a size larger than the return
|
||||||
|
// value of MaxPayloadLength, the test can panic without a helpful message.
|
||||||
func TestLightningWireProtocol(t *testing.T) {
|
func TestLightningWireProtocol(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@ -353,6 +365,17 @@ func TestLightningWireProtocol(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1/2 chance empty upfront shutdown script.
|
||||||
|
if r.Intn(2) == 0 {
|
||||||
|
req.UpfrontShutdownScript, err = randDeliveryAddress(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to generate delivery address: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
req.UpfrontShutdownScript = []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
v[0] = reflect.ValueOf(req)
|
v[0] = reflect.ValueOf(req)
|
||||||
},
|
},
|
||||||
MsgAcceptChannel: func(v []reflect.Value, r *rand.Rand) {
|
MsgAcceptChannel: func(v []reflect.Value, r *rand.Rand) {
|
||||||
@ -403,6 +426,17 @@ func TestLightningWireProtocol(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1/2 chance empty upfront shutdown script.
|
||||||
|
if r.Intn(2) == 0 {
|
||||||
|
req.UpfrontShutdownScript, err = randDeliveryAddress(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to generate delivery address: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
req.UpfrontShutdownScript = []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
v[0] = reflect.ValueOf(req)
|
v[0] = reflect.ValueOf(req)
|
||||||
},
|
},
|
||||||
MsgFundingCreated: func(v []reflect.Value, r *rand.Rand) {
|
MsgFundingCreated: func(v []reflect.Value, r *rand.Rand) {
|
||||||
|
@ -122,6 +122,12 @@ type OpenChannel struct {
|
|||||||
// Currently, the least significant bit of this bit field indicates the
|
// Currently, the least significant bit of this bit field indicates the
|
||||||
// initiator of the channel wishes to advertise this channel publicly.
|
// initiator of the channel wishes to advertise this channel publicly.
|
||||||
ChannelFlags FundingFlag
|
ChannelFlags FundingFlag
|
||||||
|
|
||||||
|
// UpfrontShutdownScript is the script to which the channel funds should
|
||||||
|
// be paid when mutually closing the channel. This field is optional, and
|
||||||
|
// and has a length prefix, so a zero will be written if it is not set
|
||||||
|
// and its length followed by the script will be written if it is set.
|
||||||
|
UpfrontShutdownScript DeliveryAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// A compile time check to ensure OpenChannel implements the lnwire.Message
|
// A compile time check to ensure OpenChannel implements the lnwire.Message
|
||||||
@ -153,6 +159,7 @@ func (o *OpenChannel) Encode(w io.Writer, pver uint32) error {
|
|||||||
o.HtlcPoint,
|
o.HtlcPoint,
|
||||||
o.FirstCommitmentPoint,
|
o.FirstCommitmentPoint,
|
||||||
o.ChannelFlags,
|
o.ChannelFlags,
|
||||||
|
o.UpfrontShutdownScript,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +169,7 @@ func (o *OpenChannel) Encode(w io.Writer, pver uint32) error {
|
|||||||
//
|
//
|
||||||
// This is part of the lnwire.Message interface.
|
// This is part of the lnwire.Message interface.
|
||||||
func (o *OpenChannel) Decode(r io.Reader, pver uint32) error {
|
func (o *OpenChannel) Decode(r io.Reader, pver uint32) error {
|
||||||
return ReadElements(r,
|
if err := ReadElements(r,
|
||||||
o.ChainHash[:],
|
o.ChainHash[:],
|
||||||
o.PendingChannelID[:],
|
o.PendingChannelID[:],
|
||||||
&o.FundingAmount,
|
&o.FundingAmount,
|
||||||
@ -181,7 +188,18 @@ func (o *OpenChannel) Decode(r io.Reader, pver uint32) error {
|
|||||||
&o.HtlcPoint,
|
&o.HtlcPoint,
|
||||||
&o.FirstCommitmentPoint,
|
&o.FirstCommitmentPoint,
|
||||||
&o.ChannelFlags,
|
&o.ChannelFlags,
|
||||||
)
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for the optional upfront shutdown script field. If it is not there,
|
||||||
|
// silence the EOF error.
|
||||||
|
err := ReadElement(r, &o.UpfrontShutdownScript)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MsgType returns the MessageType code which uniquely identifies this message
|
// MsgType returns the MessageType code which uniquely identifies this message
|
||||||
@ -198,5 +216,10 @@ func (o *OpenChannel) MsgType() MessageType {
|
|||||||
// This is part of the lnwire.Message interface.
|
// This is part of the lnwire.Message interface.
|
||||||
func (o *OpenChannel) MaxPayloadLength(uint32) uint32 {
|
func (o *OpenChannel) MaxPayloadLength(uint32) uint32 {
|
||||||
// (32 * 2) + (8 * 6) + (4 * 1) + (2 * 2) + (33 * 6) + 1
|
// (32 * 2) + (8 * 6) + (4 * 1) + (2 * 2) + (33 * 6) + 1
|
||||||
return 319
|
var length uint32 = 319 // base length
|
||||||
|
|
||||||
|
// Upfront shutdown script max length.
|
||||||
|
length += 2 + deliveryAddressMaxSize
|
||||||
|
|
||||||
|
return length
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,15 @@ type Shutdown struct {
|
|||||||
// p2wpkh.
|
// p2wpkh.
|
||||||
type DeliveryAddress []byte
|
type DeliveryAddress []byte
|
||||||
|
|
||||||
|
// deliveryAddressMaxSize is the maximum expected size in bytes of a
|
||||||
|
// DeliveryAddress based on the types of scripts we know.
|
||||||
|
// Following are the known scripts and their sizes in bytes.
|
||||||
|
// - pay to witness script hash: 34
|
||||||
|
// - pay to pubkey hash: 25
|
||||||
|
// - pay to script hash: 22
|
||||||
|
// - pay to witness pubkey hash: 22.
|
||||||
|
const deliveryAddressMaxSize = 34
|
||||||
|
|
||||||
// NewShutdown creates a new Shutdown message.
|
// NewShutdown creates a new Shutdown message.
|
||||||
func NewShutdown(cid ChannelID, addr DeliveryAddress) *Shutdown {
|
func NewShutdown(cid ChannelID, addr DeliveryAddress) *Shutdown {
|
||||||
return &Shutdown{
|
return &Shutdown{
|
||||||
@ -71,11 +80,8 @@ func (s *Shutdown) MaxPayloadLength(pver uint32) uint32 {
|
|||||||
// Len - 2 bytes
|
// Len - 2 bytes
|
||||||
length += 2
|
length += 2
|
||||||
|
|
||||||
// ScriptPubKey - 34 bytes for pay to witness script hash
|
// ScriptPubKey - maximum delivery address size.
|
||||||
length += 34
|
length += deliveryAddressMaxSize
|
||||||
|
|
||||||
// NOTE: pay to pubkey hash is 25 bytes, pay to script hash is 22
|
|
||||||
// bytes, and pay to witness pubkey hash is 22 bytes in length.
|
|
||||||
|
|
||||||
return length
|
return length
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user