Merge pull request #5669 from Roasbeef/explicit-chan-type

lnwire+funding: introduce new protocol extension for explicit commitment type negotiation
This commit is contained in:
Olaoluwa Osuntokun 2021-08-31 19:28:59 -07:00 committed by GitHub
commit d263a01a2d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1488 additions and 901 deletions

View File

@ -57,6 +57,9 @@ Signed base64 encoded PSBT or hex encoded raw wire TX (or path to text file): `
// the user from choosing a large file by accident and running into out
// of memory issues or other weird errors.
psbtMaxFileSize = 1024 * 1024
channelTypeTweakless = "tweakless"
channelTypeAnchors = "anchors"
)
// TODO(roasbeef): change default number of confirmations
@ -200,6 +203,12 @@ var openChannelCommand = cli.Command{
Usage: "(optional) the maximum value in msat that " +
"can be pending within the channel at any given time",
},
cli.StringFlag{
Name: "channel_type",
Usage: fmt.Sprintf("(optional) the type of channel to "+
"propose to the remote peer (%q, %q)",
channelTypeTweakless, channelTypeAnchors),
},
},
Action: actionDecorator(openChannel),
}
@ -307,6 +316,19 @@ func openChannel(ctx *cli.Context) error {
req.Private = ctx.Bool("private")
// Parse the channel type and map it to its RPC representation.
channelType := ctx.String("channel_type")
switch channelType {
case "":
break
case channelTypeTweakless:
req.CommitmentType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
case channelTypeAnchors:
req.CommitmentType = lnrpc.CommitmentType_ANCHORS
default:
return fmt.Errorf("unsupported channel type %v", channelType)
}
// PSBT funding is a more involved, interactive process that is too
// large to also fit into this already long function.
if ctx.Bool("psbt") {

View File

@ -38,6 +38,17 @@ instantaneous. Read the [guide on leader
election](https://github.com/lightningnetwork/lnd/blob/master/docs/leader_election.md)
for more information.
## Protocol Extensions
### Explicit Channel Negotiation
[A new protocol extension has been added known as explicit channel negotiation]
(https://github.com/lightningnetwork/lnd/pull/5669). This allows a channel
initiator to signal their desired channel type to use with the remote peer. If
the remote peer supports said channel type and agrees, the previous implicit
negotiation based on the shared set of feature bits is bypassed, and the
proposed channel type is used.
## RPC Server
* [Return payment address and add index from
@ -238,6 +249,7 @@ change](https://github.com/lightningnetwork/lnd/pull/5613).
* Eugene Siegel
* Martin Habovstiak
* Oliver Gugger
* Wilmer Paulino
* xanoni
* Yong Yu
* Zero-1729

View File

@ -60,4 +60,8 @@ var defaultSetDesc = setDesc{
lnwire.AMPRequired: {
SetInvoiceAmp: {}, // 9A
},
lnwire.ExplicitChannelTypeOptional: {
SetInit: {}, // I
SetNodeAnn: {}, // N
},
}

View File

@ -58,9 +58,13 @@ var deps = depDesc{
lnwire.AnchorsOptional: {
lnwire.StaticRemoteKeyOptional: {},
},
lnwire.AnchorsZeroFeeHtlcTxOptional: {
lnwire.StaticRemoteKeyOptional: {},
},
lnwire.AMPOptional: {
lnwire.PaymentAddrOptional: {},
},
lnwire.ExplicitChannelTypeOptional: {},
}
// ValidateDeps asserts that a feature vector sets all features and their

View File

@ -0,0 +1,120 @@
package funding
import (
"errors"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
// errUnsupportedExplicitNegotiation is an error returned when explicit
// channel commitment negotiation is attempted but either peer of the
// channel does not support it.
errUnsupportedExplicitNegotiation = errors.New("explicit channel " +
"type negotiation not supported")
// errUnsupportedCommitmentType is an error returned when a specific
// channel commitment type is being explicitly negotiated but either
// peer of the channel does not support it.
errUnsupportedChannelType = errors.New("requested channel type " +
"not supported")
)
// negotiateCommitmentType negotiates the commitment type of a newly opened
// channel. If a channelType is provided, explicit negotiation for said type
// will be attempted if the set of both local and remote features support it.
// Otherwise, implicit negotiation will be attempted.
func negotiateCommitmentType(channelType *lnwire.ChannelType,
local, remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) {
if channelType != nil {
if !hasFeatures(local, remote, lnwire.ExplicitChannelTypeOptional) {
return 0, errUnsupportedExplicitNegotiation
}
return explicitNegotiateCommitmentType(
*channelType, local, remote,
)
}
return implicitNegotiateCommitmentType(local, remote), nil
}
// explicitNegotiateCommitmentType attempts to explicitly negotiate for a
// specific channel type. Since the channel type is comprised of a set of even
// feature bits, we also make sure each feature is supported by both peers. An
// error is returned if either peer does not support said channel type.
func explicitNegotiateCommitmentType(channelType lnwire.ChannelType,
local, remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) {
channelFeatures := lnwire.RawFeatureVector(channelType)
switch {
// Anchors zero fee + static remote key features only.
case channelFeatures.OnlyContains(
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
// Static remote key feature only.
case channelFeatures.OnlyContains(lnwire.StaticRemoteKeyRequired):
if !hasFeatures(local, remote, lnwire.StaticRemoteKeyOptional) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeTweakless, nil
// No features, use legacy commitment type.
case channelFeatures.IsEmpty():
return lnwallet.CommitmentTypeLegacy, nil
default:
return 0, errUnsupportedChannelType
}
}
// implicitNegotiateCommitmentType negotiates the commitment type of a channel
// implicitly by choosing the latest type supported by the local and remote
// fetures.
func implicitNegotiateCommitmentType(local,
remote *lnwire.FeatureVector) lnwallet.CommitmentType {
// If both peers are signalling support for anchor commitments with
// zero-fee HTLC transactions, we'll use this type.
if hasFeatures(local, remote, lnwire.AnchorsZeroFeeHtlcTxOptional) {
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx
}
// Since we don't want to support the "legacy" anchor type, we will fall
// back to static remote key if the nodes don't support the zero fee
// HTLC tx anchor type.
//
// If both nodes are signaling the proper feature bit for tweakless
// commitments, we'll use that.
if hasFeatures(local, remote, lnwire.StaticRemoteKeyOptional) {
return lnwallet.CommitmentTypeTweakless
}
// Otherwise we'll fall back to the legacy type.
return lnwallet.CommitmentTypeLegacy
}
// hasFeatures determines whether a set of features is supported by both the set
// of local and remote features.
func hasFeatures(local, remote *lnwire.FeatureVector,
features ...lnwire.FeatureBit) bool {
for _, feature := range features {
if !local.HasFeature(feature) || !remote.HasFeature(feature) {
return false
}
}
return true
}

View File

@ -0,0 +1,191 @@
package funding
import (
"testing"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// TestCommitmentTypeNegotiation tests all of the possible paths of a channel
// commitment type negotiation.
func TestCommitmentTypeNegotiation(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
channelFeatures *lnwire.RawFeatureVector
localFeatures *lnwire.RawFeatureVector
remoteFeatures *lnwire.RawFeatureVector
expectsRes lnwallet.CommitmentType
expectsErr error
}{
{
name: "explicit missing remote negotiation feature",
channelFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
),
localFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
),
expectsErr: errUnsupportedExplicitNegotiation,
},
{
name: "explicit missing remote commitment feature",
channelFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
),
localFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.ExplicitChannelTypeOptional,
),
expectsErr: errUnsupportedChannelType,
},
{
name: "explicit anchors",
channelFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
),
localFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
expectsRes: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx,
expectsErr: nil,
},
{
name: "explicit tweakless",
channelFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
),
localFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
expectsRes: lnwallet.CommitmentTypeTweakless,
expectsErr: nil,
},
{
name: "explicit legacy",
channelFeatures: lnwire.NewRawFeatureVector(),
localFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
expectsRes: lnwallet.CommitmentTypeLegacy,
expectsErr: nil,
},
{
name: "implicit anchors",
channelFeatures: nil,
localFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
expectsRes: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx,
expectsErr: nil,
},
{
name: "implicit tweakless",
channelFeatures: nil,
localFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
),
expectsRes: lnwallet.CommitmentTypeTweakless,
expectsErr: nil,
},
{
name: "implicit legacy",
channelFeatures: nil,
localFeatures: lnwire.NewRawFeatureVector(),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
),
expectsRes: lnwallet.CommitmentTypeLegacy,
expectsErr: nil,
},
}
for _, testCase := range testCases {
testCase := testCase
ok := t.Run(testCase.name, func(t *testing.T) {
localFeatures := lnwire.NewFeatureVector(
testCase.localFeatures, lnwire.Features,
)
remoteFeatures := lnwire.NewFeatureVector(
testCase.remoteFeatures, lnwire.Features,
)
var channelType *lnwire.ChannelType
if testCase.channelFeatures != nil {
channelType = new(lnwire.ChannelType)
*channelType = lnwire.ChannelType(
*testCase.channelFeatures,
)
}
localType, err := negotiateCommitmentType(
channelType, localFeatures, remoteFeatures,
)
require.Equal(t, testCase.expectsErr, err)
remoteType, err := negotiateCommitmentType(
channelType, remoteFeatures, localFeatures,
)
require.Equal(t, testCase.expectsErr, err)
if testCase.expectsErr != nil {
return
}
require.Equal(t, testCase.expectsRes, localType)
require.Equal(t, testCase.expectsRes, remoteType)
})
if !ok {
return
}
}
}

View File

@ -147,6 +147,10 @@ type reservationWithCtx struct {
// maxLocalCsv is the maximum csv we will accept from the remote.
maxLocalCsv uint16
// channelType is the explicit channel type proposed by the initiator of
// the channel.
channelType *lnwire.ChannelType
updateMtx sync.RWMutex
lastUpdated time.Time
@ -240,6 +244,11 @@ type InitFundingMsg struct {
// protocol.
PendingChanID [32]byte
// ChannelType allows the caller to use an explicit channel type for the
// funding negotiation. This type will only be observed if BOTH sides
// support explicit channel type negotiation.
ChannelType *lnwire.ChannelType
// Updates is a channel which updates to the opening status of the channel
// are sent on.
Updates chan *lnrpc.OpenStatusUpdate
@ -1132,43 +1141,6 @@ func (f *Manager) ProcessFundingMsg(msg lnwire.Message, peer lnpeer.Peer) {
}
}
// commitmentType returns the commitment type to use for the channel, based on
// the features the two peers have available.
func commitmentType(localFeatures,
remoteFeatures *lnwire.FeatureVector) lnwallet.CommitmentType {
// If both peers are signalling support for anchor commitments with
// zero-fee HTLC transactions, we'll use this type.
localZeroFee := localFeatures.HasFeature(
lnwire.AnchorsZeroFeeHtlcTxOptional,
)
remoteZeroFee := remoteFeatures.HasFeature(
lnwire.AnchorsZeroFeeHtlcTxOptional,
)
if localZeroFee && remoteZeroFee {
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx
}
// Since we don't want to support the "legacy" anchor type, we will
// fall back to static remote key if the nodes don't support the zero
// fee HTLC tx anchor type.
localTweakless := localFeatures.HasFeature(
lnwire.StaticRemoteKeyOptional,
)
remoteTweakless := remoteFeatures.HasFeature(
lnwire.StaticRemoteKeyOptional,
)
// If both nodes are signaling the proper feature bit for tweakless
// copmmitments, we'll use that.
if localTweakless && remoteTweakless {
return lnwallet.CommitmentTypeTweakless
}
// Otherwise we'll fall back to the legacy type.
return lnwallet.CommitmentTypeLegacy
}
// handleFundingOpen creates an initial 'ChannelReservation' within the wallet,
// then responds to the source peer with an accept channel message progressing
// the funding workflow.
@ -1309,10 +1281,19 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
//
// Before we init the channel, we'll also check to see what commitment
// format we can use with this peer. This is dependent on *both* us and
// the remote peer are signaling the proper feature bit.
commitType := commitmentType(
peer.LocalFeatures(), peer.RemoteFeatures(),
// the remote peer are signaling the proper feature bit if we're using
// implicit negotiation, and simply the channel type sent over if we're
// using explicit negotiation.
commitType, err := negotiateCommitmentType(
msg.ChannelType, peer.LocalFeatures(), peer.RemoteFeatures(),
)
if err != nil {
// TODO(roasbeef): should be using soft errors
log.Errorf("channel type negotiation failed: %v", err)
f.failFundingFlow(peer, msg.PendingChannelID, err)
return
}
chainHash := chainhash.Hash(msg.ChainHash)
req := &lnwallet.InitFundingReserveMsg{
ChainHash: &chainHash,
@ -1442,6 +1423,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
remoteMaxValue: remoteMaxValue,
remoteMaxHtlcs: maxHtlcs,
maxLocalCsv: f.cfg.MaxLocalCSVDelay,
channelType: msg.ChannelType,
err: make(chan error, 1),
peer: peer,
}
@ -1514,6 +1496,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
HtlcPoint: ourContribution.HtlcBasePoint.PubKey,
FirstCommitmentPoint: ourContribution.FirstCommitmentPoint,
UpfrontShutdownScript: ourContribution.UpfrontShutdown,
ChannelType: msg.ChannelType,
}
if err := peer.SendMessage(true, &fundingAccept); err != nil {
@ -1545,6 +1528,29 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
log.Infof("Recv'd fundingResponse for pending_id(%x)",
pendingChanID[:])
// We'll want to quickly check that ChannelType echoed by the channel
// request recipient matches what we proposed.
//
// TODO: Return errors as funding.Error to give context to remote peer?
if resCtx.channelType != nil {
if msg.ChannelType == nil {
err := errors.New("explicit channel type not echoed back")
f.failFundingFlow(peer, msg.PendingChannelID, err)
return
}
proposedFeatures := lnwire.RawFeatureVector(*resCtx.channelType)
ackedFeatures := lnwire.RawFeatureVector(*msg.ChannelType)
if !proposedFeatures.Equals(&ackedFeatures) {
err := errors.New("channel type mismatch")
f.failFundingFlow(peer, msg.PendingChannelID, err)
return
}
} else if msg.ChannelType != nil {
err := errors.New("received unexpected channel type")
f.failFundingFlow(peer, msg.PendingChannelID, err)
return
}
// The required number of confirmations should not be greater than the
// maximum number of confirmations required by the ChainNotifier to
// properly dispatch confirmations.
@ -3178,9 +3184,15 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
// Before we init the channel, we'll also check to see what commitment
// format we can use with this peer. This is dependent on *both* us and
// the remote peer are signaling the proper feature bit.
commitType := commitmentType(
msg.Peer.LocalFeatures(), msg.Peer.RemoteFeatures(),
commitType, err := negotiateCommitmentType(
msg.ChannelType, msg.Peer.LocalFeatures(),
msg.Peer.RemoteFeatures(),
)
if err != nil {
log.Errorf("channel type negotiation failed: %v", err)
msg.Err <- err
return
}
// First, we'll query the fee estimator for a fee that should get the
// commitment transaction confirmed by the next few blocks (conf target
@ -3272,6 +3284,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
remoteMaxValue: maxValue,
remoteMaxHtlcs: maxHtlcs,
maxLocalCsv: maxCSV,
channelType: msg.ChannelType,
reservation: reservation,
peer: msg.Peer,
updates: msg.Updates,
@ -3315,6 +3328,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
FirstCommitmentPoint: ourContribution.FirstCommitmentPoint,
ChannelFlags: channelFlags,
UpfrontShutdownScript: shutdown,
ChannelType: msg.ChannelType,
}
if err := msg.Peer.SendMessage(true, &fundingOpen); err != nil {
e := fmt.Errorf("unable to send funding request message: %v",

File diff suppressed because it is too large Load Diff

View File

@ -1115,11 +1115,16 @@ message HTLC {
}
enum CommitmentType {
/*
Returned when the commitment type isn't known or unavailable.
*/
UNKNOWN_COMMITMENT_TYPE = 0;
/*
A channel using the legacy commitment format having tweaked to_remote
keys.
*/
LEGACY = 0;
LEGACY = 1;
/*
A channel that uses the modern commitment format where the key in the
@ -1127,19 +1132,14 @@ enum CommitmentType {
up and recovery easier as when the channel is closed, the funds go
directly to that key.
*/
STATIC_REMOTE_KEY = 1;
STATIC_REMOTE_KEY = 2;
/*
A channel that uses a commitment format that has anchor outputs on the
commitments, allowing fee bumping after a force close transaction has
been broadcast.
*/
ANCHORS = 2;
/*
Returned when the commitment type isn't known or unavailable.
*/
UNKNOWN_COMMITMENT_TYPE = 999;
ANCHORS = 3;
}
message ChannelConstraints {
@ -1867,6 +1867,12 @@ message OpenChannelRequest {
transaction.
*/
uint32 max_local_csv = 17;
/*
The explicit commitment type to use. Note this field will only be used if
the remote peer supports explicit channel negotiation.
*/
CommitmentType commitment_type = 18;
}
message OpenStatusUpdate {
oneof update {

View File

@ -3502,13 +3502,13 @@
"lnrpcCommitmentType": {
"type": "string",
"enum": [
"UNKNOWN_COMMITMENT_TYPE",
"LEGACY",
"STATIC_REMOTE_KEY",
"ANCHORS",
"UNKNOWN_COMMITMENT_TYPE"
"ANCHORS"
],
"default": "LEGACY",
"description": " - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast.\n - UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable."
"default": "UNKNOWN_COMMITMENT_TYPE",
"description": " - UNKNOWN_COMMITMENT_TYPE: Returned when the commitment type isn't known or unavailable.\n - LEGACY: A channel using the legacy commitment format having tweaked to_remote\nkeys.\n - STATIC_REMOTE_KEY: A channel that uses the modern commitment format where the key in the\noutput of the remote party does not change each state. This makes back\nup and recovery easier as when the channel is closed, the funds go\ndirectly to that key.\n - ANCHORS: A channel that uses a commitment format that has anchor outputs on the\ncommitments, allowing fee bumping after a force close transaction has\nbeen broadcast."
},
"lnrpcConnectPeerRequest": {
"type": "object",
@ -4923,6 +4923,10 @@
"type": "integer",
"format": "int64",
"description": "Max local csv is the maximum csv delay we will allow for our own commitment\ntransaction."
},
"commitment_type": {
"$ref": "#/definitions/lnrpcCommitmentType",
"description": "The explicit commitment type to use. Note this field will only be used if\nthe remote peer supports explicit channel negotiation."
}
}
},

View File

@ -982,6 +982,10 @@ type OpenChannelParams struct {
// SatPerVByte is the amount of satoshis to spend in chain fees per virtual
// byte of the transaction.
SatPerVByte btcutil.Amount
// CommitmentType is the commitment type that should be used for the
// channel to be opened.
CommitmentType lnrpc.CommitmentType
}
// OpenChannel attempts to open a channel between srcNode and destNode with the
@ -1025,6 +1029,7 @@ func (n *NetworkHarness) OpenChannel(srcNode, destNode *HarnessNode,
RemoteMaxHtlcs: uint32(p.RemoteMaxHtlcs),
FundingShim: p.FundingShim,
SatPerByte: int64(p.SatPerVByte),
CommitmentType: p.CommitmentType,
}
respStream, err := srcNode.OpenChannel(ctx, openReq)

View File

@ -855,7 +855,10 @@ func testChanRestoreScenario(t *harnessTest, net *lntest.NetworkHarness,
"--maxbackoff=1s",
}
if testCase.anchorCommit {
nodeArgs = append(nodeArgs, commitTypeAnchors.Args()...)
anchorNodeArgs := nodeArgsForCommitType(
lnrpc.CommitmentType_ANCHORS,
)
nodeArgs = append(nodeArgs, anchorNodeArgs...)
}
// First, we'll create a brand new node we'll use within the test. If

View File

@ -81,10 +81,10 @@ func testChannelBalance(net *lntest.NetworkHarness, t *harnessTest) {
// As this is a single funder channel, Alice's balance should be
// exactly 0.5 BTC since now state transitions have taken place yet.
checkChannelBalance(net.Alice, amount-cType.calcStaticFee(0), 0)
checkChannelBalance(net.Alice, amount-calcStaticFee(cType, 0), 0)
// Ensure Bob currently has no available balance within the channel.
checkChannelBalance(net.Bob, 0, amount-cType.calcStaticFee(0))
checkChannelBalance(net.Bob, 0, amount-calcStaticFee(cType, 0))
// Finally close the channel between Alice and Bob, asserting that the
// channel has been properly closed on-chain.
@ -174,11 +174,11 @@ func testChannelUnsettledBalance(net *lntest.NetworkHarness, t *harnessTest) {
// Check alice's channel balance, which should have zero remote and zero
// pending balance.
checkChannelBalance(net.Alice, chanAmt-cType.calcStaticFee(0), 0, 0, 0)
checkChannelBalance(net.Alice, chanAmt-calcStaticFee(cType, 0), 0, 0, 0)
// Check carol's channel balance, which should have zero local and zero
// pending balance.
checkChannelBalance(carol, 0, chanAmt-cType.calcStaticFee(0), 0, 0)
checkChannelBalance(carol, 0, chanAmt-calcStaticFee(cType, 0), 0, 0)
// Channel should be ready for payments.
const (
@ -258,7 +258,7 @@ func testChannelUnsettledBalance(net *lntest.NetworkHarness, t *harnessTest) {
// Check alice's channel balance, which should have a remote unsettled
// balance that equals to the amount of invoices * payAmt. The remote
// balance remains zero.
aliceLocal := chanAmt - cType.calcStaticFee(0) - numInvoices*payAmt
aliceLocal := chanAmt - calcStaticFee(cType, 0) - numInvoices*payAmt
checkChannelBalance(net.Alice, aliceLocal, 0, 0, numInvoices*payAmt)
// Check carol's channel balance, which should have a local unsettled

View File

@ -76,7 +76,7 @@ func testCommitmentTransactionDeadline(net *lntest.NetworkHarness,
setupNode := func(name string) *lntest.HarnessNode {
// Create the node.
args := []string{"--hodl.exit-settle"}
args = append(args, commitTypeAnchors.Args()...)
args = append(args, nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)...)
node := net.NewNode(t.t, name, args)
// Send some coins to the node.
@ -244,9 +244,9 @@ func calculateTxnsFeeRate(t *testing.T,
func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) {
// We'll test the scenario for some of the commitment types, to ensure
// outputs can be swept.
commitTypes := []commitType{
commitTypeLegacy,
commitTypeAnchors,
commitTypes := []lnrpc.CommitmentType{
lnrpc.CommitmentType_LEGACY,
lnrpc.CommitmentType_ANCHORS,
}
for _, channelType := range commitTypes {
@ -261,7 +261,7 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) {
success := t.t.Run(testName, func(t *testing.T) {
ht := newHarnessTest(t, net)
args := channelType.Args()
args := nodeArgsForCommitType(channelType)
alice := net.NewNode(ht.t, "Alice", args)
defer shutdownAndAssert(net, ht, alice)
@ -292,7 +292,7 @@ func testChannelForceClosure(net *lntest.NetworkHarness, t *harnessTest) {
}
func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest,
alice, carol *lntest.HarnessNode, channelType commitType) {
alice, carol *lntest.HarnessNode, channelType lnrpc.CommitmentType) {
ctxb := context.Background()
@ -407,7 +407,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest,
// sweep the HTLC second level output one block earlier (than the
// nursery that waits an additional block, and handles non-anchor
// channels). So we set a maturity height that is one less.
if channelType == commitTypeAnchors {
if channelType == lnrpc.CommitmentType_ANCHORS {
htlcCsvMaturityHeight = padCLTV(
startHeight + defaultCLTV + defaultCSV,
)
@ -495,7 +495,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest,
// also expect the anchor sweep tx to be in the mempool.
expectedTxes := 1
expectedFeeRate := commitFeeRate
if channelType == commitTypeAnchors {
if channelType == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 2
expectedFeeRate = actualFeeRate
}
@ -528,7 +528,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest,
// If we expect anchors, add alice's anchor to our expected set of
// reports.
if channelType == commitTypeAnchors {
if channelType == lnrpc.CommitmentType_ANCHORS {
aliceReports[aliceAnchor.OutPoint.String()] = &lnrpc.Resolution{
ResolutionType: lnrpc.ResolutionType_ANCHOR,
Outcome: lnrpc.ResolutionOutcome_CLAIMED,
@ -585,7 +585,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest,
"limbo")
}
expectedRecoveredBalance := int64(0)
if channelType == commitTypeAnchors {
if channelType == lnrpc.CommitmentType_ANCHORS {
expectedRecoveredBalance = anchorSize
}
if forceClose.RecoveredBalance != expectedRecoveredBalance {
@ -634,7 +634,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest,
)
// If we have anchors, add an anchor resolution for carol.
if channelType == commitTypeAnchors {
if channelType == lnrpc.CommitmentType_ANCHORS {
carolReports[carolAnchor.OutPoint.String()] = &lnrpc.Resolution{
ResolutionType: lnrpc.ResolutionType_ANCHOR,
Outcome: lnrpc.ResolutionOutcome_CLAIMED,
@ -709,7 +709,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest,
"limbo")
}
expectedRecoveredBalance := int64(0)
if channelType == commitTypeAnchors {
if channelType == lnrpc.CommitmentType_ANCHORS {
expectedRecoveredBalance = anchorSize
}
if forceClose.RecoveredBalance != expectedRecoveredBalance {
@ -943,7 +943,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest,
expectedTxes = numInvoices
// In case of anchors, the timeout txs will be aggregated into one.
if channelType == commitTypeAnchors {
if channelType == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 1
}
@ -961,7 +961,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest,
// this is an anchor channel, the transactions are aggregated by the
// sweeper into one.
numInputs := 1
if channelType == commitTypeAnchors {
if channelType == lnrpc.CommitmentType_ANCHORS {
numInputs = numInvoices + 1
}
@ -1089,7 +1089,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest,
// Advance the chain until just before the 2nd-layer CSV delays expire.
// For anchor channels thhis is one block earlier.
numBlocks := uint32(defaultCSV - 1)
if channelType == commitTypeAnchors {
if channelType == lnrpc.CommitmentType_ANCHORS {
numBlocks = defaultCSV - 2
}
@ -1320,7 +1320,7 @@ func channelForceClosureTest(net *lntest.NetworkHarness, t *harnessTest,
// In addition, if this is an anchor-enabled channel, further add the
// anchor size.
if channelType == commitTypeAnchors {
if channelType == lnrpc.CommitmentType_ANCHORS {
carolExpectedBalance += btcutil.Amount(anchorSize)
}

View File

@ -29,19 +29,19 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
// Run through the test with combinations of all the different
// commitment types.
allTypes := []commitType{
commitTypeLegacy,
commitTypeTweakless,
commitTypeAnchors,
allTypes := []lnrpc.CommitmentType{
lnrpc.CommitmentType_LEGACY,
lnrpc.CommitmentType_STATIC_REMOTE_KEY,
lnrpc.CommitmentType_ANCHORS,
}
// testFunding is a function closure that takes Carol and Dave's
// commitment types and test the funding flow.
testFunding := func(carolCommitType, daveCommitType commitType) {
testFunding := func(carolCommitType, daveCommitType lnrpc.CommitmentType) {
// Based on the current tweak variable for Carol, we'll
// preferentially signal the legacy commitment format. We do
// the same for Dave shortly below.
carolArgs := carolCommitType.Args()
carolArgs := nodeArgsForCommitType(carolCommitType)
carol := net.NewNode(t.t, "Carol", carolArgs)
defer shutdownAndAssert(net, t, carol)
@ -49,7 +49,7 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
// fund the channel.
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol)
daveArgs := daveCommitType.Args()
daveArgs := nodeArgsForCommitType(daveCommitType)
dave := net.NewNode(t.t, "Dave", daveArgs)
defer shutdownAndAssert(net, t, dave)
@ -81,20 +81,20 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
// Dave supports anchors, type will be what
// Carol supports.
case commitTypeAnchors:
case lnrpc.CommitmentType_ANCHORS:
// Dave only supports tweakless, channel will
// be downgraded to this type if Carol supports
// anchors.
case commitTypeTweakless:
if expType == commitTypeAnchors {
expType = commitTypeTweakless
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
if expType == lnrpc.CommitmentType_ANCHORS {
expType = lnrpc.CommitmentType_STATIC_REMOTE_KEY
}
// Dave only supoprts legacy type, channel will
// be downgraded to this type.
case commitTypeLegacy:
expType = commitTypeLegacy
case lnrpc.CommitmentType_LEGACY:
expType = lnrpc.CommitmentType_LEGACY
default:
t.Fatalf("invalid commit type %v", daveCommitType)
@ -103,13 +103,13 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
// Check that the signalled type matches what we
// expect.
switch {
case expType == commitTypeAnchors &&
case expType == lnrpc.CommitmentType_ANCHORS &&
chansCommitType == lnrpc.CommitmentType_ANCHORS:
case expType == commitTypeTweakless &&
case expType == lnrpc.CommitmentType_STATIC_REMOTE_KEY &&
chansCommitType == lnrpc.CommitmentType_STATIC_REMOTE_KEY:
case expType == commitTypeLegacy &&
case expType == lnrpc.CommitmentType_LEGACY &&
chansCommitType == lnrpc.CommitmentType_LEGACY:
default:
@ -221,7 +221,7 @@ func basicChannelFundingTest(t *harnessTest, net *lntest.NetworkHarness,
// With the channel open, ensure that the amount specified above has
// properly been pushed to Bob.
aliceLocalBalance := chanAmt - pushAmt - cType.calcStaticFee(0)
aliceLocalBalance := chanAmt - pushAmt - calcStaticFee(cType, 0)
checkChannelBalance(
alice, aliceChannelBalance, aliceLocalBalance, pushAmt,
)
@ -342,8 +342,8 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
//
// Note that atm we haven't obtained the chanPoint yet, so we use the
// type directly.
cType := commitTypeTweakless
carolLocalBalance := chanAmt - pushAmt - cType.calcStaticFee(0)
cType := lnrpc.CommitmentType_STATIC_REMOTE_KEY
carolLocalBalance := chanAmt - pushAmt - calcStaticFee(cType, 0)
checkChannelBalance(carol, 0, 0, carolLocalBalance, pushAmt)
// For Alice, her local/remote balances should be zero, and the

View File

@ -39,7 +39,7 @@ func testHtlcErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) {
t.Fatalf("unable to get channel type: %v", err)
}
commitFee := cType.calcStaticFee(0)
commitFee := calcStaticFee(cType, 0)
assertBaseBalance := func() {
// Alice has opened a channel with Bob with zero push amount, so
// it's remote balance is zero.

View File

@ -23,7 +23,7 @@ import (
// case of anchor channels, the second-level spends can also be aggregated and
// properly feebumped, so we'll check that as well.
func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest,
alice, bob *lntest.HarnessNode, c commitType) {
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
const finalCltvDelta = 40
ctxb := context.Background()
@ -188,7 +188,7 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest,
// Bob's force close transaction should now be found in the mempool. If
// there are anchors, we also expect Bob's anchor sweep.
expectedTxes := 1
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 2
}
@ -262,7 +262,7 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest,
// one, the same is the case for the timeout transactions. In this case
// Carol will also sweep her anchor output in a separate tx (since it
// will be low fee).
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 4
}
@ -298,7 +298,7 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest,
// In case of anchor we expect all the timeout and success second
// levels to be aggregated into one tx. For earlier channel types, they
// will be separate transactions.
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
require.Len(t.t, timeoutTxs, 1)
require.Len(t.t, successTxs, 1)
} else {
@ -374,7 +374,7 @@ func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest,
// Mining one additional block, Bob's second level tx is mature, and he
// can sweep the output.
case c == commitTypeAnchors:
case c == lnrpc.CommitmentType_ANCHORS:
_ = mineBlocks(t, net, 1, 1)
// In case this is a non-anchor channel type, we must mine 2 blocks, as

View File

@ -20,7 +20,7 @@ import (
// preimage via the witness beacon, we properly settle the HTLC on-chain using
// the HTLC success transaction in order to ensure we don't lose any funds.
func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest,
alice, bob *lntest.HarnessNode, c commitType) {
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
ctxb := context.Background()
@ -86,13 +86,14 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest,
// At this point, Bob decides that he wants to exit the channel
// immediately, so he force closes his commitment transaction.
bobForceClose := closeChannelAndAssertType(
t, net, bob, aliceChanPoint, c == commitTypeAnchors, true,
t, net, bob, aliceChanPoint,
c == lnrpc.CommitmentType_ANCHORS, true,
)
// Alice will sweep her commitment output immediately. If there are
// anchors, Alice will also sweep hers.
expectedTxes := 1
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 2
}
_, err = waitForNTxsInMempool(
@ -161,7 +162,7 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest,
// If there are anchors on the commitment, Bob will also sweep his
// anchor.
expectedTxes = 2
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 3
}
txes, err := getNTxsFromMempool(
@ -188,7 +189,7 @@ func testMultiHopHtlcLocalChainClaim(net *lntest.NetworkHarness, t *harnessTest,
// If this is a channel of the anchor type, we will subtract one block
// from the default CSV, as the Sweeper will handle the input, and the Sweeper
// sweeps the input as soon as the lock expires.
case commitTypeAnchors:
case lnrpc.CommitmentType_ANCHORS:
secondLevelMaturity = defaultCSV - 1
// For non-anchor channel types, the nursery will handle sweeping the

View File

@ -21,7 +21,7 @@ import (
// canceled backwards. Once the timeout has been reached, then we should sweep
// it on-chain, and cancel the HTLC backwards.
func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest,
alice, bob *lntest.HarnessNode, c commitType) {
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
ctxb := context.Background()
@ -104,7 +104,7 @@ func testMultiHopHtlcLocalTimeout(net *lntest.NetworkHarness, t *harnessTest,
// Bob's force close transaction should now be found in the mempool. If
// there are anchors, we also expect Bob's anchor sweep.
expectedTxes := 1
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 2
}

View File

@ -22,7 +22,7 @@ import (
// extract the preimage from the sweep transaction, and finish settling the
// HTLC backwards into the route.
func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest,
alice, bob *lntest.HarnessNode, c commitType) {
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
ctxb := context.Background()
@ -113,7 +113,7 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest,
// transaction in order to go to the chain and sweep her HTLC. If there
// are anchors, Carol also sweeps hers.
expectedTxes := 1
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 2
}
_, err = getNTxsFromMempool(
@ -150,7 +150,7 @@ func testMultiHopReceiverChainClaim(net *lntest.NetworkHarness, t *harnessTest,
// and settle the HTLC back off-chain. Bob will also sweep his anchor,
// if present.
expectedTxes = 2
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 3
}
txes, err := getNTxsFromMempool(

View File

@ -20,7 +20,7 @@ import (
// HTLC directly on-chain using the preimage in order to ensure that we don't
// lose any funds.
func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest,
alice, bob *lntest.HarnessNode, c commitType) {
alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
ctxb := context.Background()
@ -86,7 +86,8 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
// immediately force close the channel by broadcast her commitment
// transaction.
aliceForceClose := closeChannelAndAssertType(
t, net, alice, aliceChanPoint, c == commitTypeAnchors, true,
t, net, alice, aliceChanPoint,
c == lnrpc.CommitmentType_ANCHORS, true,
)
// Wait for the channel to be marked pending force close.
@ -96,7 +97,7 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
// After closeChannelAndAssertType returns, it has mined a block so now
// bob will attempt to redeem his anchor commitment (if the channel
// type is of that type).
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
_, err = waitForNTxsInMempool(
net.Miner.Client, 1, minerMempoolTimeout,
)
@ -146,7 +147,7 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
require.NoError(t.t, err)
expectedTxes := 1
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 2
}
@ -186,7 +187,7 @@ func testMultiHopHtlcRemoteChainClaim(net *lntest.NetworkHarness, t *harnessTest
// the output is not timelocked since Carol was the one force closing.
// If there are anchors, Bob should also sweep his.
expectedTxes = 2
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 3
}
txes, err := getNTxsFromMempool(

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
@ -17,7 +18,7 @@ import (
// that's timed out. At this point, the node should timeout the HTLC using the
// HTLC timeout transaction, then cancel it backwards as normal.
func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
t *harnessTest, alice, bob *lntest.HarnessNode, c commitType) {
t *harnessTest, alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
ctxb := context.Background()
@ -72,7 +73,8 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
// force close the Bob -> Carol channel. This should trigger contract
// resolution mode for both of them.
closeChannelAndAssertType(
t, net, bob, bobChanPoint, c == commitTypeAnchors, true,
t, net, bob, bobChanPoint,
c == lnrpc.CommitmentType_ANCHORS, true,
)
// At this point, Bob should have a pending force close channel as he
@ -93,7 +95,7 @@ func testMultiHopLocalForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
// We'll mine defaultCSV blocks in order to generate the sweep
// transaction of Bob's funding output. If there are anchors, mine
// Carol's anchor sweep too.
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
_, err = waitForTxInMempool(net.Miner.Client, minerMempoolTimeout)
require.NoError(t.t, err)
}

View File

@ -20,7 +20,7 @@ import (
// transaction once the timeout has expired. Once we sweep the transaction, we
// should also cancel back the initial HTLC.
func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
t *harnessTest, alice, bob *lntest.HarnessNode, c commitType) {
t *harnessTest, alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) {
ctxb := context.Background()
@ -84,7 +84,8 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
// expired HTLC on Carol's version of the commitment transaction. If
// Carol has an anchor, it will be swept too.
closeChannelAndAssertType(
t, net, carol, bobChanPoint, c == commitTypeAnchors, true,
t, net, carol, bobChanPoint,
c == lnrpc.CommitmentType_ANCHORS, true,
)
// At this point, Bob should have a pending force close channel as
@ -95,7 +96,7 @@ func testMultiHopRemoteForceCloseOnChainHtlcTimeout(net *lntest.NetworkHarness,
// Bob can sweep his output immediately. If there is an anchor, Bob will
// sweep that as well.
expectedTxes := 1
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
expectedTxes = 2
}

View File

@ -19,7 +19,7 @@ func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) {
type testCase struct {
name string
test func(net *lntest.NetworkHarness, t *harnessTest, alice,
bob *lntest.HarnessNode, c commitType)
bob *lntest.HarnessNode, c lnrpc.CommitmentType)
}
subTests := []testCase{
@ -68,19 +68,19 @@ func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) {
},
}
commitTypes := []commitType{
commitTypeLegacy,
commitTypeAnchors,
commitTypes := []lnrpc.CommitmentType{
lnrpc.CommitmentType_LEGACY,
lnrpc.CommitmentType_ANCHORS,
}
for _, commitType := range commitTypes {
commitType := commitType
testName := fmt.Sprintf("committype=%v", commitType.String())
commitType := commitType
success := t.t.Run(testName, func(t *testing.T) {
ht := newHarnessTest(t, net)
args := commitType.Args()
args := nodeArgsForCommitType(commitType)
alice := net.NewNode(t, "Alice", args)
defer shutdownAndAssert(net, ht, alice)
@ -205,7 +205,7 @@ func checkPaymentStatus(node *lntest.HarnessNode, preimage lntypes.Preimage,
}
func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
alice, bob *lntest.HarnessNode, carolHodl bool, c commitType) (
alice, bob *lntest.HarnessNode, carolHodl bool, c lnrpc.CommitmentType) (
*lnrpc.ChannelPoint, *lnrpc.ChannelPoint, *lntest.HarnessNode) {
ctxb := context.Background()
@ -224,7 +224,8 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
aliceChanPoint := openChannelAndAssert(
t, net, alice, bob,
lntest.OpenChannelParams{
Amt: chanAmt,
Amt: chanAmt,
CommitmentType: c,
},
)
@ -243,7 +244,7 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
// Next, we'll create a new node "carol" and have Bob connect to her. If
// the carolHodl flag is set, we'll make carol always hold onto the
// HTLC, this way it'll force Bob to go to chain to resolve the HTLC.
carolFlags := c.Args()
carolFlags := nodeArgsForCommitType(c)
if carolHodl {
carolFlags = append(carolFlags, "--hodl.exit-settle")
}
@ -264,7 +265,8 @@ func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
bobChanPoint := openChannelAndAssert(
t, net, bob, carol,
lntest.OpenChannelParams{
Amt: chanAmt,
Amt: chanAmt,
CommitmentType: c,
},
)
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)

View File

@ -167,7 +167,7 @@ func testCPFP(net *lntest.NetworkHarness, t *harnessTest) {
// wallet.
func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) {
// Start two nodes supporting anchor channels.
args := commitTypeAnchors.Args()
args := nodeArgsForCommitType(lnrpc.CommitmentType_ANCHORS)
alice := net.NewNode(t.t, "Alice", args)
defer shutdownAndAssert(net, t, alice)

View File

@ -208,45 +208,15 @@ func getChanInfo(node *lntest.HarnessNode) (*lnrpc.Channel, error) {
return channelInfo.Channels[0], nil
}
// commitType is a simple enum used to run though the basic funding flow with
// different commitment formats.
type commitType byte
const (
// commitTypeLegacy is the old school commitment type.
commitTypeLegacy commitType = iota
// commiTypeTweakless is the commitment type where the remote key is
// static (non-tweaked).
commitTypeTweakless
// commitTypeAnchors is the kind of commitment that has extra outputs
// used for anchoring down to commitment using CPFP.
commitTypeAnchors
)
// String returns that name of the commitment type.
func (c commitType) String() string {
switch c {
case commitTypeLegacy:
return "legacy"
case commitTypeTweakless:
return "tweakless"
case commitTypeAnchors:
return "anchors"
default:
return "invalid"
}
}
// Args returns the command line flag to supply to enable this commitment type.
func (c commitType) Args() []string {
switch c {
case commitTypeLegacy:
// nodeArgsForCommitType returns the command line flag to supply to enable this
// commitment type.
func nodeArgsForCommitType(commitType lnrpc.CommitmentType) []string {
switch commitType {
case lnrpc.CommitmentType_LEGACY:
return []string{"--protocol.legacy.committweak"}
case commitTypeTweakless:
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
return []string{}
case commitTypeAnchors:
case lnrpc.CommitmentType_ANCHORS:
return []string{"--protocol.anchors"}
}
@ -256,7 +226,7 @@ func (c commitType) Args() []string {
// calcStaticFee calculates appropriate fees for commitment transactions. This
// function provides a simple way to allow test balance assertions to take fee
// calculations into account.
func (c commitType) calcStaticFee(numHTLCs int) btcutil.Amount {
func calcStaticFee(c lnrpc.CommitmentType, numHTLCs int) btcutil.Amount {
const htlcWeight = input.HTLCWeight
var (
feePerKw = chainfee.SatPerKVByte(50000).FeePerKWeight()
@ -268,7 +238,7 @@ func (c commitType) calcStaticFee(numHTLCs int) btcutil.Amount {
// the value of the two anchors to the resulting fee the initiator
// pays. In addition the fee rate is capped at 10 sat/vbyte for anchor
// channels.
if c == commitTypeAnchors {
if c == lnrpc.CommitmentType_ANCHORS {
feePerKw = chainfee.SatPerKVByte(
lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte * 1000,
).FeePerKWeight()
@ -283,7 +253,7 @@ func (c commitType) calcStaticFee(numHTLCs int) btcutil.Amount {
// channelCommitType retrieves the active channel commitment type for the given
// chan point.
func channelCommitType(node *lntest.HarnessNode,
chanPoint *lnrpc.ChannelPoint) (commitType, error) {
chanPoint *lnrpc.ChannelPoint) (lnrpc.CommitmentType, error) {
ctxb := context.Background()
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
@ -296,21 +266,7 @@ func channelCommitType(node *lntest.HarnessNode,
for _, c := range channels.Channels {
if c.ChannelPoint == txStr(chanPoint) {
switch c.CommitmentType {
// If the anchor output size is non-zero, we are
// dealing with the anchor type.
case lnrpc.CommitmentType_ANCHORS:
return commitTypeAnchors, nil
// StaticRemoteKey means it is tweakless,
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
return commitTypeTweakless, nil
// Otherwise legacy.
default:
return commitTypeLegacy, nil
}
return c.CommitmentType, nil
}
}

View File

@ -2,11 +2,11 @@ package lnwire
import (
"bytes"
"fmt"
"io"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/tlv"
)
// AcceptChannel is the message Bob sends to Alice after she initiates the
@ -95,6 +95,10 @@ type AcceptChannel struct {
// and its length followed by the script will be written if it is set.
UpfrontShutdownScript DeliveryAddress
// ChannelType is the explicit channel type the initiator wishes to
// open.
ChannelType *ChannelType
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
@ -117,11 +121,11 @@ var _ Message = (*AcceptChannel)(nil)
//
// This is part of the lnwire.Message interface.
func (a *AcceptChannel) Encode(w *bytes.Buffer, pver uint32) error {
// Since the upfront script is encoded as a TLV record, concatenate it
// with the ExtraData, and write them as one.
tlvRecords, err := packShutdownScript(
a.UpfrontShutdownScript, a.ExtraData,
)
recordProducers := []tlv.RecordProducer{&a.UpfrontShutdownScript}
if a.ChannelType != nil {
recordProducers = append(recordProducers, a.ChannelType)
}
err := EncodeMessageExtraData(&a.ExtraData, recordProducers...)
if err != nil {
return err
}
@ -182,7 +186,7 @@ func (a *AcceptChannel) Encode(w *bytes.Buffer, pver uint32) error {
return err
}
return WriteBytes(w, tlvRecords)
return WriteBytes(w, a.ExtraData)
}
// Decode deserializes the serialized AcceptChannel stored in the passed
@ -220,74 +224,26 @@ func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error {
return err
}
a.UpfrontShutdownScript, a.ExtraData, err = parseShutdownScript(
tlvRecords,
// Next we'll parse out the set of known records, keeping the raw tlv
// bytes untouched to ensure we don't drop any bytes erroneously.
var chanType ChannelType
typeMap, err := tlvRecords.ExtractRecords(
&a.UpfrontShutdownScript, &chanType,
)
if err != nil {
return err
}
// Set the corresponding TLV types if they were included in the stream.
if val, ok := typeMap[ChannelTypeRecordType]; ok && val == nil {
a.ChannelType = &chanType
}
a.ExtraData = tlvRecords
return nil
}
// packShutdownScript takes an upfront shutdown script and an opaque data blob
// and concatenates them.
func packShutdownScript(addr DeliveryAddress, extraData ExtraOpaqueData) (
ExtraOpaqueData, error) {
// We'll always write the upfront shutdown script record, regardless of
// the script being empty.
var tlvRecords ExtraOpaqueData
// Pack it into a data blob as a TLV record.
err := tlvRecords.PackRecords(addr.NewRecord())
if err != nil {
return nil, fmt.Errorf("unable to pack upfront shutdown "+
"script as TLV record: %v", err)
}
// Concatenate the remaining blob with the shutdown script record.
tlvRecords = append(tlvRecords, extraData...)
return tlvRecords, nil
}
// parseShutdownScript reads and extract the upfront shutdown script from the
// passe data blob. It returns the script, if any, and the remainder of the
// data blob.
//
// This can be used to parse extra data for the OpenChannel and AcceptChannel
// messages, where the shutdown script is mandatory if extra TLV data is
// present.
func parseShutdownScript(tlvRecords ExtraOpaqueData) (DeliveryAddress,
ExtraOpaqueData, error) {
// If no TLV data is present there can't be any script available.
if len(tlvRecords) == 0 {
return nil, tlvRecords, nil
}
// Otherwise the shutdown script MUST be present.
var addr DeliveryAddress
tlvs, err := tlvRecords.ExtractRecords(addr.NewRecord())
if err != nil {
return nil, nil, err
}
// Not among TLV records, this means the data was invalid.
if _, ok := tlvs[DeliveryAddrType]; !ok {
return nil, nil, fmt.Errorf("no shutdown script in non-empty " +
"data blob")
}
// Now that we have retrieved the address (which can be zero-length),
// we'll remove the bytes encoding it from the TLV data before
// returning it.
addrLen := len(addr)
tlvRecords = tlvRecords[addrLen+2:]
return addr, tlvRecords, nil
}
// MsgType returns the MessageType code which uniquely identifies this message
// as an AcceptChannel on the wire.
//

58
lnwire/channel_type.go Normal file
View File

@ -0,0 +1,58 @@
package lnwire
import (
"io"
"github.com/lightningnetwork/lnd/tlv"
)
const (
// ChannelTypeRecordType is the type of the experimental record used
// to denote which channel type is being negotiated.
ChannelTypeRecordType tlv.Type = 1
)
// ChannelType represents a specific channel type as a set of feature bits that
// comprise it.
type ChannelType RawFeatureVector
// featureBitLen returns the length in bytes of the encoded feature bits.
func (c ChannelType) featureBitLen() uint64 {
fv := RawFeatureVector(c)
return uint64(fv.SerializeSize())
}
// Record returns a TLV record that can be used to encode/decode the channel
// type from a given TLV stream.
func (c *ChannelType) Record() tlv.Record {
return tlv.MakeDynamicRecord(
ChannelTypeRecordType, c, c.featureBitLen, channelTypeEncoder,
channelTypeDecoder,
)
}
// channelTypeEncoder is a custom TLV encoder for the ChannelType record.
func channelTypeEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*ChannelType); ok {
// Encode the feature bits as a byte slice without its length
// prepended, as that's already taken care of by the TLV record.
fv := RawFeatureVector(*v)
return fv.encode(w, fv.SerializeSize(), 8)
}
return tlv.NewTypeForEncodingErr(val, "lnwire.ChannelType")
}
// channelTypeDecoder is a custom TLV decoder for the ChannelType record.
func channelTypeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
if v, ok := val.(*ChannelType); ok {
fv := NewRawFeatureVector()
if err := fv.decode(r, int(l), 8); err != nil {
return err
}
*v = ChannelType(*fv)
return nil
}
return tlv.NewTypeForEncodingErr(val, "lnwire.ChannelType")
}

View File

@ -0,0 +1,28 @@
package lnwire
import (
"testing"
"github.com/stretchr/testify/require"
)
// TestChannelTypeEncodeDecode tests that we're able to properly encode and
// decode channel types within TLV streams.
func TestChannelTypeEncodeDecode(t *testing.T) {
t.Parallel()
chanType := ChannelType(*NewRawFeatureVector(
StaticRemoteKeyRequired,
AnchorsZeroFeeHtlcTxRequired,
))
var extraData ExtraOpaqueData
require.NoError(t, extraData.PackRecords(&chanType))
var chanType2 ChannelType
tlvs, err := extraData.ExtractRecords(&chanType2)
require.NoError(t, err)
require.Contains(t, tlvs, ChannelTypeRecordType)
require.Equal(t, chanType, chanType2)
}

View File

@ -2,6 +2,7 @@ package lnwire
import (
"bytes"
"fmt"
"io"
"io/ioutil"
@ -50,7 +51,17 @@ func (e *ExtraOpaqueData) Decode(r io.Reader) error {
// PackRecords attempts to encode the set of tlv records into the target
// ExtraOpaqueData instance. The records will be encoded as a raw TLV stream
// and stored within the backing slice pointer.
func (e *ExtraOpaqueData) PackRecords(records ...tlv.Record) error {
func (e *ExtraOpaqueData) PackRecords(recordProducers ...tlv.RecordProducer) error {
// First, assemble all the records passed in in series.
records := make([]tlv.Record, 0, len(recordProducers))
for _, producer := range recordProducers {
records = append(records, producer.Record())
}
// Ensure that the set of records are sorted before we encode them into
// the stream, to ensure they're canonical.
tlv.SortRecords(records)
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
@ -70,9 +81,15 @@ func (e *ExtraOpaqueData) PackRecords(records ...tlv.Record) error {
// it were a tlv stream. The set of raw parsed types is returned, and any
// passed records (if found in the stream) will be parsed into the proper
// tlv.Record.
func (e *ExtraOpaqueData) ExtractRecords(records ...tlv.Record) (
func (e *ExtraOpaqueData) ExtractRecords(recordProducers ...tlv.RecordProducer) (
tlv.TypeMap, error) {
// First, assemble all the records passed in in series.
records := make([]tlv.Record, 0, len(recordProducers))
for _, producer := range recordProducers {
records = append(records, producer.Record())
}
extraBytesReader := bytes.NewReader(*e)
tlvStream, err := tlv.NewStream(records...)
@ -82,3 +99,19 @@ func (e *ExtraOpaqueData) ExtractRecords(records ...tlv.Record) (
return tlvStream.DecodeWithParsedTypes(extraBytesReader)
}
// EncodeMessageExtraData encodes the given recordProducers into the given
// extraData.
func EncodeMessageExtraData(extraData *ExtraOpaqueData,
recordProducers ...tlv.RecordProducer) error {
// Treat extraData as a mutable reference.
if extraData == nil {
return fmt.Errorf("extra data cannot be nil")
}
// Pack in the series of TLV records into this message. The order we
// pass them in doesn't matter, as the method will ensure that things
// are all properly sorted.
return extraData.PackRecords(recordProducers...)
}

View File

@ -86,6 +86,14 @@ func TestExtraOpaqueDataEncodeDecode(t *testing.T) {
}
}
type recordProducer struct {
record tlv.Record
}
func (r *recordProducer) Record() tlv.Record {
return r.record
}
// TestExtraOpaqueDataPackUnpackRecords tests that we're able to pack a set of
// tlv.Records into a stream, and unpack them on the other side to obtain the
// same set of records.
@ -102,23 +110,23 @@ func TestExtraOpaqueDataPackUnpackRecords(t *testing.T) {
hop1 uint32 = 99
hop2 uint32
)
testRecords := []tlv.Record{
tlv.MakePrimitiveRecord(type1, &channelType1),
tlv.MakePrimitiveRecord(type2, &hop1),
testRecordsProducers := []tlv.RecordProducer{
&recordProducer{tlv.MakePrimitiveRecord(type1, &channelType1)},
&recordProducer{tlv.MakePrimitiveRecord(type2, &hop1)},
}
// Now that we have our set of sample records and types, we'll encode
// them into the passed ExtraOpaqueData instance.
var extraBytes ExtraOpaqueData
if err := extraBytes.PackRecords(testRecords...); err != nil {
if err := extraBytes.PackRecords(testRecordsProducers...); err != nil {
t.Fatalf("unable to pack records: %v", err)
}
// We'll now simulate decoding these types _back_ into records on the
// other side.
newRecords := []tlv.Record{
tlv.MakePrimitiveRecord(type1, &channelType2),
tlv.MakePrimitiveRecord(type2, &hop2),
newRecords := []tlv.RecordProducer{
&recordProducer{tlv.MakePrimitiveRecord(type1, &channelType2)},
&recordProducer{tlv.MakePrimitiveRecord(type2, &hop2)},
}
typeMap, err := extraBytes.ExtractRecords(newRecords...)
if err != nil {

View File

@ -139,6 +139,26 @@ const (
// sender-generated preimages according to BOLT XX.
AMPOptional FeatureBit = 31
// ExplicitChannelTypeRequired is a required bit that denotes that a
// connection established with this node is to use explicit channel
// commitment types for negotiation instead of the existing implicit
// negotiation methods. With this bit, there is no longer a "default"
// implicit channel commitment type, allowing a connection to
// open/maintain types of several channels over its lifetime.
//
// TODO: Decide on actual feature bit value.
ExplicitChannelTypeRequired = 2020
// ExplicitChannelTypeOptional is an optional bit that denotes that a
// connection established with this node is to use explicit channel
// commitment types for negotiation instead of the existing implicit
// negotiation methods. With this bit, there is no longer a "default"
// implicit channel commitment type, allowing a connection to
// open/maintain types of several channels over its lifetime.
//
// TODO: Decide on actual feature bit value.
ExplicitChannelTypeOptional = 2021
// maxAllowedSize is a maximum allowed size of feature vector.
//
// NOTE: Within the protocol, the maximum allowed message size is 65535
@ -184,6 +204,8 @@ var Features = map[FeatureBit]string{
WumboChannelsOptional: "wumbo-channels",
AMPRequired: "amp",
AMPOptional: "amp",
ExplicitChannelTypeOptional: "explicit-commitment-type",
ExplicitChannelTypeRequired: "explicit-commitment-type",
}
// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A
@ -192,19 +214,51 @@ var Features = map[FeatureBit]string{
// can be serialized and deserialized to/from a byte representation that is
// transmitted in Lightning network messages.
type RawFeatureVector struct {
features map[FeatureBit]bool
features map[FeatureBit]struct{}
}
// NewRawFeatureVector creates a feature vector with all of the feature bits
// given as arguments enabled.
func NewRawFeatureVector(bits ...FeatureBit) *RawFeatureVector {
fv := &RawFeatureVector{features: make(map[FeatureBit]bool)}
fv := &RawFeatureVector{features: make(map[FeatureBit]struct{})}
for _, bit := range bits {
fv.Set(bit)
}
return fv
}
// IsEmpty returns whether the feature vector contains any feature bits.
func (fv RawFeatureVector) IsEmpty() bool {
return len(fv.features) == 0
}
// OnlyContains determines whether only the specified feature bits are found.
func (fv RawFeatureVector) OnlyContains(bits ...FeatureBit) bool {
if len(bits) != len(fv.features) {
return false
}
for _, bit := range bits {
if !fv.IsSet(bit) {
return false
}
}
return true
}
// Equals determines whether two features vectors contain exactly the same
// features.
func (fv RawFeatureVector) Equals(other *RawFeatureVector) bool {
if len(fv.features) != len(other.features) {
return false
}
for bit := range fv.features {
if _, ok := other.features[bit]; !ok {
return false
}
}
return true
}
// Merges sets all feature bits in other on the receiver's feature vector.
func (fv *RawFeatureVector) Merge(other *RawFeatureVector) error {
for bit := range other.features {
@ -227,12 +281,13 @@ func (fv *RawFeatureVector) Clone() *RawFeatureVector {
// IsSet returns whether a particular feature bit is enabled in the vector.
func (fv *RawFeatureVector) IsSet(feature FeatureBit) bool {
return fv.features[feature]
_, ok := fv.features[feature]
return ok
}
// Set marks a feature as enabled in the vector.
func (fv *RawFeatureVector) Set(feature FeatureBit) {
fv.features[feature] = true
fv.features[feature] = struct{}{}
}
// SafeSet sets the chosen feature bit in the feature vector, but returns an

View File

@ -353,3 +353,47 @@ func TestFeatures(t *testing.T) {
})
}
}
func TestRawFeatureVectorOnlyContains(t *testing.T) {
t.Parallel()
features := []FeatureBit{
StaticRemoteKeyOptional,
AnchorsZeroFeeHtlcTxOptional,
ExplicitChannelTypeRequired,
}
fv := NewRawFeatureVector(features...)
require.True(t, fv.OnlyContains(features...))
require.False(t, fv.OnlyContains(features[:1]...))
}
func TestEqualRawFeatureVectors(t *testing.T) {
t.Parallel()
a := NewRawFeatureVector(
StaticRemoteKeyOptional,
AnchorsZeroFeeHtlcTxOptional,
ExplicitChannelTypeRequired,
)
b := a.Clone()
require.True(t, a.Equals(b))
b.Unset(ExplicitChannelTypeRequired)
require.False(t, a.Equals(b))
b.Set(ExplicitChannelTypeOptional)
require.False(t, a.Equals(b))
}
func TestIsEmptyFeatureVector(t *testing.T) {
t.Parallel()
fv := NewRawFeatureVector()
require.True(t, fv.IsEmpty())
fv.Set(StaticRemoteKeyOptional)
require.False(t, fv.IsEmpty())
fv.Unset(StaticRemoteKeyOptional)
require.True(t, fv.IsEmpty())
}

View File

@ -18,8 +18,8 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/tor"
"github.com/stretchr/testify/assert"
)
var (
@ -284,9 +284,7 @@ func TestLightningWireProtocol(t *testing.T) {
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))
if !assert.Equalf(t, msg, newMsg, "message mismatch") {
return false
}
@ -369,17 +367,16 @@ func TestLightningWireProtocol(t *testing.T) {
t.Fatalf("unable to generate delivery address: %v", err)
return
}
req.ChannelType = new(ChannelType)
*req.ChannelType = ChannelType(*randRawFeatureVector(r))
} else {
req.UpfrontShutdownScript = []byte{}
}
// 1/2 chance how having more TLV data after the
// shutdown script.
// 1/2 chance additional TLV data.
if r.Intn(2) == 0 {
// TLV type 1 of length 2.
req.ExtraData = []byte{1, 2, 0xff, 0xff}
} else {
req.ExtraData = []byte{}
req.ExtraData = []byte{0xfd, 0x00, 0xff, 0x00}
}
v[0] = reflect.ValueOf(req)
@ -439,16 +436,16 @@ func TestLightningWireProtocol(t *testing.T) {
t.Fatalf("unable to generate delivery address: %v", err)
return
}
req.ChannelType = new(ChannelType)
*req.ChannelType = ChannelType(*randRawFeatureVector(r))
} else {
req.UpfrontShutdownScript = []byte{}
}
// 1/2 chance how having more TLV data after the
// shutdown script.
// 1/2 chance additional TLV data.
if r.Intn(2) == 0 {
// TLV type 1 of length 2.
req.ExtraData = []byte{1, 2, 0xff, 0xff}
} else {
req.ExtraData = []byte{}
req.ExtraData = []byte{0xfd, 0x00, 0xff, 0x00}
}
v[0] = reflect.ValueOf(req)

View File

@ -7,6 +7,7 @@ import (
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/tlv"
)
// FundingFlag represents the possible bit mask values for the ChannelFlags
@ -130,6 +131,10 @@ type OpenChannel struct {
// and its length followed by the script will be written if it is set.
UpfrontShutdownScript DeliveryAddress
// ChannelType is the explicit channel type the initiator wishes to
// open.
ChannelType *ChannelType
// ExtraData is the set of data that was appended to this message to
// fill out the full maximum transport message size. These fields can
// be used to specify optional data such as custom TLV fields.
@ -150,13 +155,12 @@ var _ Message = (*OpenChannel)(nil)
// implementation. Serialization will observe the rules defined by the passed
// protocol version.
//
// This is part of the lnwire.Message interface.
func (o *OpenChannel) Encode(w *bytes.Buffer, pver uint32) error {
// Since the upfront script is encoded as a TLV record, concatenate it
// with the ExtraData, and write them as one.
tlvRecords, err := packShutdownScript(
o.UpfrontShutdownScript, o.ExtraData,
)
recordProducers := []tlv.RecordProducer{&o.UpfrontShutdownScript}
if o.ChannelType != nil {
recordProducers = append(recordProducers, o.ChannelType)
}
err := EncodeMessageExtraData(&o.ExtraData, recordProducers...)
if err != nil {
return err
}
@ -234,7 +238,7 @@ func (o *OpenChannel) Encode(w *bytes.Buffer, pver uint32) error {
return err
}
return WriteBytes(w, tlvRecords)
return WriteBytes(w, o.ExtraData)
}
// Decode deserializes the serialized OpenChannel stored in the passed
@ -276,13 +280,23 @@ func (o *OpenChannel) Decode(r io.Reader, pver uint32) error {
return err
}
o.UpfrontShutdownScript, o.ExtraData, err = parseShutdownScript(
tlvRecords,
// Next we'll parse out the set of known records, keeping the raw tlv
// bytes untouched to ensure we don't drop any bytes erroneously.
var chanType ChannelType
typeMap, err := tlvRecords.ExtractRecords(
&o.UpfrontShutdownScript, &chanType,
)
if err != nil {
return err
}
// Set the corresponding TLV types if they were included in the stream.
if val, ok := typeMap[ChannelTypeRecordType]; ok && val == nil {
o.ChannelType = &chanType
}
o.ExtraData = tlvRecords
return nil
}

View File

@ -24,11 +24,11 @@ const (
// p2wpkh.
type DeliveryAddress []byte
// NewRecord returns a TLV record that can be used to encode the delivery
// address within the ExtraData TLV stream. This was intorudced in order to
// Record returns a TLV record that can be used to encode the delivery
// address within the ExtraData TLV stream. This was introduced in order to
// allow the OpenChannel/AcceptChannel messages to properly be extended with
// TLV types.
func (d *DeliveryAddress) NewRecord() tlv.Record {
func (d *DeliveryAddress) Record() tlv.Record {
addrBytes := (*[]byte)(d)
return tlv.MakeDynamicRecord(

View File

@ -15,13 +15,13 @@ func TestDeliveryAddressEncodeDecode(t *testing.T) {
)
var extraData ExtraOpaqueData
err := extraData.PackRecords(addr.NewRecord())
err := extraData.PackRecords(&addr)
if err != nil {
t.Fatal(err)
}
var addr2 DeliveryAddress
tlvs, err := extraData.ExtractRecords(addr2.NewRecord())
tlvs, err := extraData.ExtractRecords(&addr2)
if err != nil {
t.Fatal(err)
}

View File

@ -193,9 +193,11 @@ func TestWriteRawFeatureVector(t *testing.T) {
require.Equal(t, ErrNilFeatureVector, err)
// Create a raw feature vector.
feature := &RawFeatureVector{features: map[FeatureBit]bool{
InitialRoutingSync: true, // FeatureBit 3.
}}
feature := &RawFeatureVector{
features: map[FeatureBit]struct{}{
InitialRoutingSync: {}, // FeatureBit 3.
},
}
expectedBytes := []byte{
0, 1, // First two bytes encode the length.
8, // Last byte encodes the feature bit (1 << 3).

View File

@ -1912,6 +1912,33 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
err)
}
var channelType *lnwire.ChannelType
switch in.CommitmentType {
case lnrpc.CommitmentType_UNKNOWN_COMMITMENT_TYPE:
break
case lnrpc.CommitmentType_LEGACY:
channelType = new(lnwire.ChannelType)
*channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector())
case lnrpc.CommitmentType_STATIC_REMOTE_KEY:
channelType = new(lnwire.ChannelType)
*channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
))
case lnrpc.CommitmentType_ANCHORS:
channelType = new(lnwire.ChannelType)
*channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
))
default:
return nil, fmt.Errorf("unhandled request channel type %v",
in.CommitmentType)
}
// Instruct the server to trigger the necessary events to attempt to
// open a new channel. A stream is returned in place, this stream will
// be used to consume updates of the state of the pending channel.
@ -1929,6 +1956,7 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
MaxValueInFlight: maxValue,
MaxHtlcs: maxHtlcs,
MaxLocalCsv: uint16(in.MaxLocalCsv),
ChannelType: channelType,
}, nil
}