server+funding: allow scid-alias, zero-conf chantypes, scid-alias

feature-bit channels

This allows opening zero-conf chan-type, scid-alias chan-type, and
scid-alias feature-bit channels. scid-alias chan-type channels are
required to be private. Two paths are available for opening a zero-conf
channel:

* explicit chan-type negotiation
* LDK carve-out where chan-types are not used, LND is on the
  receiving end, and a ChannelAcceptor is used to enable zero-conf

When a zero-conf channel is negotiated, the funding manager:
* sends a FundingLocked with an alias
* waits for a FundingLocked from the remote peer
* calls addToRouterGraph to persist the channel using our alias in
  the graph. The peer's alias is used to send them a ChannelUpdate.
* wait for six confirmations. If public, the alias edge in the
  graph is deleted and replaced (not atomically) with the confirmed
  edge. Our policy is also read-and-replaced, but the counterparty's
  policy won't exist until they send it to us.

When a scid-alias-feature channel is negotiated, the funding manager:
* sends a FundingLocked with an alias:
* calls addToRouterGraph, sends ChannelUpdate with the confirmed SCID
  since it exists.
* when six confirmations occurs, the edge is deleted and re-inserted
  since the peer may have sent us an alias ChannelUpdate that we are
  storing in the graph.

Since it is possible for a user to toggle the scid-alias-feature-bit
to on while channels exist in the funding manager, care has been taken
to ensure that an alias is ALWAYS sent in the funding_locked message
if this happens.
This commit is contained in:
eugene 2022-04-04 16:47:05 -04:00
parent 13c15e8038
commit 1aa9626606
No known key found for this signature in database
GPG Key ID: 118759E83439A9B1
19 changed files with 3883 additions and 2697 deletions

View File

@ -184,13 +184,15 @@ func TestMultipleAcceptClients(t *testing.T) {
queries = map[*lnwire.OpenChannel]*ChannelAcceptResponse{
chan1: NewChannelAcceptResponse(
true, nil, testUpfront, 1, 2, 3, 4, 5, 6,
false,
),
chan2: NewChannelAcceptResponse(
false, errChannelRejected, nil, 0, 0, 0,
0, 0, 0,
0, 0, 0, false,
),
chan3: NewChannelAcceptResponse(
false, customError, nil, 0, 0, 0, 0, 0, 0,
false,
),
}
@ -245,7 +247,7 @@ func TestInvalidResponse(t *testing.T) {
PendingChannelID: chan1,
}: NewChannelAcceptResponse(
false, errChannelRejected, nil, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0, false,
),
}
@ -288,7 +290,7 @@ func TestInvalidReserve(t *testing.T) {
DustLimit: dustLimit,
}: NewChannelAcceptResponse(
false, errChannelRejected, nil, 0, 0,
0, reserve, 0, 0,
0, reserve, 0, 0, false,
),
}

View File

@ -80,7 +80,7 @@ func (c *ChainedAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptRespon
return NewChannelAcceptResponse(
false, errChannelRejected, nil, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0, false,
)
}
}

View File

@ -62,6 +62,10 @@ type ChannelAcceptResponse struct {
// MinAcceptDepth is the minimum depth that the initiator of the
// channel should wait before considering the channel open.
MinAcceptDepth uint16
// ZeroConf indicates that the fundee wishes to send min_depth = 0 and
// request a zero-conf channel with the counter-party.
ZeroConf bool
}
// NewChannelAcceptResponse is a constructor for a channel accept response,
@ -72,7 +76,7 @@ type ChannelAcceptResponse struct {
func NewChannelAcceptResponse(accept bool, acceptErr error,
upfrontShutdown lnwire.DeliveryAddress, csvDelay, htlcLimit,
minDepth uint16, reserve btcutil.Amount, inFlight,
minHtlcIn lnwire.MilliSatoshi) *ChannelAcceptResponse {
minHtlcIn lnwire.MilliSatoshi, zeroConf bool) *ChannelAcceptResponse {
resp := &ChannelAcceptResponse{
UpfrontShutdown: upfrontShutdown,
@ -82,6 +86,7 @@ func NewChannelAcceptResponse(accept bool, acceptErr error,
HtlcLimit: htlcLimit,
MinHtlcIn: minHtlcIn,
MinAcceptDepth: minDepth,
ZeroConf: zeroConf,
}
// If we want to accept the channel, we return a response with a nil

View File

@ -20,6 +20,10 @@ const (
fieldUpfrontShutdown = "upfront shutdown"
)
var (
errZeroConf = fmt.Errorf("zero-conf set with non-zero min-depth")
)
// fieldMismatchError returns a merge error for a named field when we get two
// channel acceptor responses which have different values set.
func fieldMismatchError(name string, current, newValue interface{}) error {
@ -27,6 +31,13 @@ func fieldMismatchError(name string, current, newValue interface{}) error {
name, current, newValue)
}
// mergeBool merges two boolean values.
func mergeBool(current, newValue bool) bool {
// If either is true, return true. It is not possible to have different
// "non-zero" values like the other cases.
return current || newValue
}
// mergeInt64 merges two int64 values, failing if they have different non-zero
// values.
func mergeInt64(name string, current, newValue int64) (int64, error) {
@ -117,6 +128,13 @@ func mergeResponse(current,
}
current.MinAcceptDepth = uint16(minDepth)
current.ZeroConf = mergeBool(current.ZeroConf, newValue.ZeroConf)
// Assert that if zero-conf is set, min-depth is zero.
if current.ZeroConf && current.MinAcceptDepth != 0 {
return current, errZeroConf
}
reserve, err := mergeInt64(
fieldReserve, int64(current.Reserve), int64(newValue.Reserve),
)

View File

@ -167,6 +167,18 @@ func TestMergeResponse(t *testing.T) {
},
err: nil,
},
{
// Test the case where one response has ZeroConf set
// and another has a non-zero min depth set.
name: "zero conf conflict",
current: ChannelAcceptResponse{
ZeroConf: true,
},
new: ChannelAcceptResponse{
MinAcceptDepth: 5,
},
err: errZeroConf,
},
}
for _, test := range tests {

View File

@ -107,7 +107,7 @@ func (r *RPCAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptResponse {
// Create a rejection response which we can use for the cases where we
// reject the channel.
rejectChannel := NewChannelAcceptResponse(
false, errChannelRejected, nil, 0, 0, 0, 0, 0, 0,
false, errChannelRejected, nil, 0, 0, 0, 0, 0, 0, false,
)
// Send the request to the newRequests channel.
@ -216,6 +216,7 @@ func (r *RPCAcceptor) receiveResponses(errChan chan error,
MaxHtlcCount: resp.MaxHtlcCount,
MinHtlcIn: resp.MinHtlcIn,
MinAcceptDepth: resp.MinAcceptDepth,
ZeroConf: resp.ZeroConf,
}
// We have received a decision for one of our channel
@ -348,6 +349,7 @@ func (r *RPCAcceptor) sendAcceptRequests(errChan chan error,
btcutil.Amount(resp.ReserveSat),
lnwire.MilliSatoshi(resp.InFlightMaxMsat),
lnwire.MilliSatoshi(resp.MinHtlcIn),
resp.ZeroConf,
)
// Delete the channel from the acceptRequests map.

View File

@ -1692,8 +1692,9 @@ func (c *ChannelGraph) PruneTip() (*chainhash.Hash, uint32, error) {
// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is
// true, then when we mark these edges as zombies, we'll set up the keys such
// that we require the node that failed to send the fresh update to be the one
// that resurrects the channel from its zombie state.
func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning bool,
// that resurrects the channel from its zombie state. The markZombie bool
// denotes whether or not to mark the channel as a zombie.
func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool,
chanIDs ...uint64) error {
// TODO(roasbeef): possibly delete from node bucket if node has no more
@ -1730,7 +1731,7 @@ func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning bool,
byteOrder.PutUint64(rawChanID[:], chanID)
err := c.delChannelEdge(
edges, edgeIndex, chanIndex, zombieIndex, nodes,
rawChanID[:], true, strictZombiePruning,
rawChanID[:], markZombie, strictZombiePruning,
)
if err != nil {
return err

View File

@ -378,7 +378,7 @@ func TestEdgeInsertionDeletion(t *testing.T) {
// Next, attempt to delete the edge from the database, again this
// should proceed without any issues.
if err := graph.DeleteChannelEdges(false, chanID); err != nil {
if err := graph.DeleteChannelEdges(false, true, chanID); err != nil {
t.Fatalf("unable to delete edge: %v", err)
}
assertNoEdge(t, graph, chanID)
@ -398,7 +398,7 @@ func TestEdgeInsertionDeletion(t *testing.T) {
// Finally, attempt to delete a (now) non-existent edge within the
// database, this should result in an error.
err = graph.DeleteChannelEdges(false, chanID)
err = graph.DeleteChannelEdges(false, true, chanID)
if err != ErrEdgeNotFound {
t.Fatalf("deleting a non-existent edge should fail!")
}
@ -1993,7 +1993,7 @@ func TestFilterKnownChanIDs(t *testing.T) {
if err := graph.AddChannelEdge(&channel); err != nil {
t.Fatalf("unable to create channel edge: %v", err)
}
err := graph.DeleteChannelEdges(false, channel.ChannelID)
err := graph.DeleteChannelEdges(false, true, channel.ChannelID)
if err != nil {
t.Fatalf("unable to mark edge zombie: %v", err)
}
@ -2251,7 +2251,7 @@ func TestFetchChanInfos(t *testing.T) {
if err := graph.AddChannelEdge(&zombieChan); err != nil {
t.Fatalf("unable to create channel edge: %v", err)
}
err = graph.DeleteChannelEdges(false, zombieChan.ChannelID)
err = graph.DeleteChannelEdges(false, true, zombieChan.ChannelID)
require.NoError(t, err, "unable to delete and mark edge zombie")
edgeQuery = append(edgeQuery, zombieChanID.ToUint64())
@ -2789,7 +2789,9 @@ func TestNodeIsPublic(t *testing.T) {
// graph. This will make Alice be seen as a private node as it no longer
// has any advertised edges.
for _, graph := range graphs {
err := graph.DeleteChannelEdges(false, aliceBobEdge.ChannelID)
err := graph.DeleteChannelEdges(
false, true, aliceBobEdge.ChannelID,
)
if err != nil {
t.Fatalf("unable to remove edge: %v", err)
}
@ -2806,7 +2808,9 @@ func TestNodeIsPublic(t *testing.T) {
// completely remove the edge as it is not possible for her to know of
// it without it being advertised.
for i, graph := range graphs {
err := graph.DeleteChannelEdges(false, bobCarolEdge.ChannelID)
err := graph.DeleteChannelEdges(
false, true, bobCarolEdge.ChannelID,
)
if err != nil {
t.Fatalf("unable to remove edge: %v", err)
}
@ -2900,7 +2904,9 @@ func TestDisabledChannelIDs(t *testing.T) {
}
// Delete the channel edge and ensure it is removed from the disabled list.
if err = graph.DeleteChannelEdges(false, edgeInfo.ChannelID); err != nil {
if err = graph.DeleteChannelEdges(
false, true, edgeInfo.ChannelID,
); err != nil {
t.Fatalf("unable to delete channel edge: %v", err)
}
disabledChanIds, err = graph.DisabledChannelIDs()
@ -3111,7 +3117,7 @@ func TestGraphZombieIndex(t *testing.T) {
// If we delete the edge and mark it as a zombie, then we should expect
// to see it within the index.
err = graph.DeleteChannelEdges(false, edge.ChannelID)
err = graph.DeleteChannelEdges(false, true, edge.ChannelID)
require.NoError(t, err, "unable to mark edge as zombie")
isZombie, pubKey1, pubKey2 := graph.IsZombieEdge(edge.ChannelID)
if !isZombie {

View File

@ -24,10 +24,12 @@ var (
// 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, mustBeExplicit bool,
) (bool, *lnwire.ChannelType, lnwallet.CommitmentType, error) {
// Otherwise, implicit negotiation will be attempted. Two booleans are
// returned letting the caller know if the option-scid-alias or zero-conf
// channel types were negotiated.
func negotiateCommitmentType(channelType *lnwire.ChannelType, local,
remote *lnwire.FeatureVector, mustBeExplicit bool) (bool,
*lnwire.ChannelType, lnwallet.CommitmentType, error) {
if channelType != nil {
// If the peer does know explicit negotiation, let's attempt
@ -57,12 +59,127 @@ func negotiateCommitmentType(channelType *lnwire.ChannelType,
// 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) {
func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local,
remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) {
channelFeatures := lnwire.RawFeatureVector(channelType)
switch {
// Lease script enforcement + anchors zero fee + static remote key +
// zero conf + scid alias features only.
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.ScidAliasRequired,
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ZeroConfOptional,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeScriptEnforcedLease, nil
// Anchors zero fee + static remote key + zero conf + scid alias
// features only.
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.ScidAliasRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ZeroConfOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
// Lease script enforcement + anchors zero fee + static remote key +
// zero conf features only.
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ZeroConfOptional,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeScriptEnforcedLease, nil
// Anchors zero fee + static remote key + zero conf features only.
case channelFeatures.OnlyContains(
lnwire.ZeroConfRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ZeroConfOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
// Lease script enforcement + anchors zero fee + static remote key +
// option-scid-alias features only.
case channelFeatures.OnlyContains(
lnwire.ScidAliasRequired,
lnwire.ScriptEnforcedLeaseRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ScidAliasOptional,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeScriptEnforcedLease, nil
// Anchors zero fee + static remote key + option-scid-alias features
// only.
case channelFeatures.OnlyContains(
lnwire.ScidAliasRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.StaticRemoteKeyRequired,
):
if !hasFeatures(
local, remote,
lnwire.ScidAliasOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.StaticRemoteKeyOptional,
) {
return 0, errUnsupportedChannelType
}
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
// Lease script enforcement + anchors zero fee + static remote key
// features only.
case channelFeatures.OnlyContains(

View File

@ -21,6 +21,8 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
remoteFeatures *lnwire.RawFeatureVector
expectsCommitType lnwallet.CommitmentType
expectsChanType lnwire.ChannelType
zeroConf bool
scidAlias bool
expectsErr error
}{
{
@ -81,6 +83,134 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
),
expectsErr: errUnsupportedChannelType,
},
{
name: "explicit zero-conf script enforced",
channelFeatures: lnwire.NewRawFeatureVector(
lnwire.ZeroConfRequired,
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.ScriptEnforcedLeaseRequired,
),
localFeatures: lnwire.NewRawFeatureVector(
lnwire.ZeroConfOptional,
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.ExplicitChannelTypeOptional,
),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.ZeroConfOptional,
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.ExplicitChannelTypeOptional,
),
expectsCommitType: lnwallet.CommitmentTypeScriptEnforcedLease,
expectsChanType: lnwire.ChannelType(
*lnwire.NewRawFeatureVector(
lnwire.ZeroConfRequired,
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.ScriptEnforcedLeaseRequired,
),
),
zeroConf: true,
expectsErr: nil,
},
{
name: "explicit zero-conf anchors",
channelFeatures: lnwire.NewRawFeatureVector(
lnwire.ZeroConfRequired,
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
),
localFeatures: lnwire.NewRawFeatureVector(
lnwire.ZeroConfOptional,
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.ZeroConfOptional,
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
expectsCommitType: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx,
expectsChanType: lnwire.ChannelType(
*lnwire.NewRawFeatureVector(
lnwire.ZeroConfRequired,
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
),
),
zeroConf: true,
expectsErr: nil,
},
{
name: "explicit scid-alias script enforced",
channelFeatures: lnwire.NewRawFeatureVector(
lnwire.ScidAliasRequired,
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.ScriptEnforcedLeaseRequired,
),
localFeatures: lnwire.NewRawFeatureVector(
lnwire.ScidAliasOptional,
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.ExplicitChannelTypeOptional,
),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.ScidAliasOptional,
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ScriptEnforcedLeaseOptional,
lnwire.ExplicitChannelTypeOptional,
),
expectsCommitType: lnwallet.CommitmentTypeScriptEnforcedLease,
expectsChanType: lnwire.ChannelType(
*lnwire.NewRawFeatureVector(
lnwire.ScidAliasRequired,
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
lnwire.ScriptEnforcedLeaseRequired,
),
),
scidAlias: true,
expectsErr: nil,
},
{
name: "explicit scid-alias anchors",
channelFeatures: lnwire.NewRawFeatureVector(
lnwire.ScidAliasRequired,
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
),
localFeatures: lnwire.NewRawFeatureVector(
lnwire.ScidAliasOptional,
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
remoteFeatures: lnwire.NewRawFeatureVector(
lnwire.ScidAliasOptional,
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
lnwire.ExplicitChannelTypeOptional,
),
expectsCommitType: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx,
expectsChanType: lnwire.ChannelType(
*lnwire.NewRawFeatureVector(
lnwire.ScidAliasRequired,
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
),
),
scidAlias: true,
expectsErr: nil,
},
{
name: "explicit anchors",
channelFeatures: lnwire.NewRawFeatureVector(
@ -212,16 +342,50 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
*testCase.channelFeatures,
)
}
_, localChanType, localCommitType, err := negotiateCommitmentType(
_, lChan, lCommit, err := negotiateCommitmentType(
channelType, localFeatures, remoteFeatures,
testCase.mustBeExplicit,
)
var (
localZc bool
localScid bool
remoteZc bool
remoteScid bool
)
if lChan != nil {
localFv := lnwire.RawFeatureVector(*lChan)
localZc = localFv.IsSet(
lnwire.ZeroConfRequired,
)
localScid = localFv.IsSet(
lnwire.ScidAliasRequired,
)
}
require.Equal(t, testCase.zeroConf, localZc)
require.Equal(t, testCase.scidAlias, localScid)
require.Equal(t, testCase.expectsErr, err)
_, remoteChanType, remoteCommitType, err := negotiateCommitmentType(
_, rChan, rCommit, err := negotiateCommitmentType(
channelType, remoteFeatures, localFeatures,
testCase.mustBeExplicit,
)
if rChan != nil {
remoteFv := lnwire.RawFeatureVector(*rChan)
remoteZc = remoteFv.IsSet(
lnwire.ZeroConfRequired,
)
remoteScid = remoteFv.IsSet(
lnwire.ScidAliasRequired,
)
}
require.Equal(t, testCase.zeroConf, remoteZc)
require.Equal(t, testCase.scidAlias, remoteScid)
require.Equal(t, testCase.expectsErr, err)
if testCase.expectsErr != nil {
@ -229,20 +393,20 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
}
require.Equal(
t, testCase.expectsCommitType, localCommitType,
t, testCase.expectsCommitType, lCommit,
testCase.name,
)
require.Equal(
t, testCase.expectsCommitType, remoteCommitType,
t, testCase.expectsCommitType, rCommit,
testCase.name,
)
require.Equal(
t, testCase.expectsChanType, *localChanType,
t, testCase.expectsChanType, *lChan,
testCase.name,
)
require.Equal(
t, testCase.expectsChanType, *remoteChanType,
t, testCase.expectsChanType, *rChan,
testCase.name,
)
})

View File

@ -19,3 +19,31 @@ type Controller interface {
// represents a pending channel in the Controller implementation.
IsPendingChannel([32]byte, lnpeer.Peer) bool
}
// aliasHandler is an interface that abstracts the managing of aliases.
type aliasHandler interface {
// RequestAlias lets the funding manager request a unique SCID alias to
// use in the funding_locked message.
RequestAlias() (lnwire.ShortChannelID, error)
// PutPeerAlias lets the funding manager store the received alias SCID
// in the funding_locked message.
PutPeerAlias(lnwire.ChannelID, lnwire.ShortChannelID) error
// GetPeerAlias lets the funding manager lookup the received alias SCID
// from the funding_locked message. This is not the same as GetAliases
// which retrieves OUR aliases for a given channel.
GetPeerAlias(lnwire.ChannelID) (lnwire.ShortChannelID, error)
// AddLocalAlias persists an alias to an underlying alias store.
AddLocalAlias(lnwire.ShortChannelID, lnwire.ShortChannelID, bool) error
// GetAliases returns the set of aliases given the main SCID of a
// channel. This SCID will be an alias for zero-conf channels and will
// be the confirmed SCID otherwise.
GetAliases(lnwire.ShortChannelID) []lnwire.ShortChannelID
// DeleteSixConfs removes the passed SCID from one of the underlying
// alias store's indices.
DeleteSixConfs(lnwire.ShortChannelID) error
}

File diff suppressed because it is too large Load Diff

View File

@ -115,8 +115,48 @@ var (
testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey}
fundingNetParams = chainreg.BitcoinTestNetParams
alias = lnwire.ShortChannelID{
BlockHeight: 16_000_000,
TxIndex: 0,
TxPosition: 0,
}
)
type mockAliasMgr struct{}
func (m *mockAliasMgr) RequestAlias() (lnwire.ShortChannelID, error) {
return alias, nil
}
func (m *mockAliasMgr) PutPeerAlias(lnwire.ChannelID,
lnwire.ShortChannelID) error {
return nil
}
func (m *mockAliasMgr) GetPeerAlias(lnwire.ChannelID) (lnwire.ShortChannelID,
error) {
return alias, nil
}
func (m *mockAliasMgr) AddLocalAlias(lnwire.ShortChannelID,
lnwire.ShortChannelID, bool) error {
return nil
}
func (m *mockAliasMgr) GetAliases(
lnwire.ShortChannelID) []lnwire.ShortChannelID {
return []lnwire.ShortChannelID{alias}
}
func (m *mockAliasMgr) DeleteSixConfs(lnwire.ShortChannelID) error {
return nil
}
type mockNotifier struct {
oneConfChannel chan *chainntnfs.TxConfirmation
sixConfChannel chan *chainntnfs.TxConfirmation
@ -199,6 +239,8 @@ type testNode struct {
mockChanEvent *mockChanEvent
testDir string
shutdownChannel chan struct{}
reportScidChan chan struct{}
localFeatures []lnwire.FeatureBit
remoteFeatures []lnwire.FeatureBit
remotePeer *testNode
@ -234,7 +276,9 @@ func (n *testNode) QuitSignal() <-chan struct{} {
}
func (n *testNode) LocalFeatures() *lnwire.FeatureVector {
return lnwire.NewFeatureVector(nil, nil)
return lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(n.localFeatures...), nil,
)
}
func (n *testNode) RemoteFeatures() *lnwire.FeatureVector {
@ -307,10 +351,13 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
epochChan: make(chan *chainntnfs.BlockEpoch, 2),
}
aliasMgr := &mockAliasMgr{}
sentMessages := make(chan lnwire.Message)
sentAnnouncements := make(chan lnwire.Message)
publTxChan := make(chan *wire.MsgTx, 1)
shutdownChan := make(chan struct{})
reportScidChan := make(chan struct{})
wc := &mock.WalletController{
RootKey: alicePrivKey,
@ -382,14 +429,16 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
return lnwire.NodeAnnouncement{}, nil
},
TempChanIDSeed: chanIDSeed,
FindChannel: func(chanID lnwire.ChannelID) (
*channeldb.OpenChannel, error) {
dbChannels, err := cdb.FetchAllChannels()
FindChannel: func(node *btcec.PublicKey,
chanID lnwire.ChannelID) (*channeldb.OpenChannel,
error) {
nodeChans, err := cdb.FetchOpenChannels(node)
if err != nil {
return nil, err
}
for _, channel := range dbChannels {
for _, channel := range nodeChans {
if chanID.IsChanPoint(&channel.FundingOutpoint) {
return channel, nil
}
@ -432,6 +481,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
return nil
},
ReportShortChanID: func(wire.OutPoint) error {
reportScidChan <- struct{}{}
return nil
},
PublishTransaction: func(txn *wire.MsgTx, _ string) error {
@ -450,6 +500,12 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
OpenChannelPredicate: chainedAcceptor,
NotifyPendingOpenChannelEvent: evt.NotifyPendingOpenChannelEvent,
RegisteredChains: chainreg.NewChainRegistry(),
DeleteAliasEdge: func(scid lnwire.ShortChannelID) (
*channeldb.ChannelEdgePolicy, error) {
return nil, nil
},
AliasManager: aliasMgr,
}
for _, op := range options {
@ -473,6 +529,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
mockChanEvent: evt,
testDir: tempTestDir,
shutdownChannel: shutdownChan,
reportScidChan: reportScidChan,
addr: addr,
}
@ -542,6 +599,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
},
DefaultMinHtlcIn: 5,
RequiredRemoteMaxValue: oldCfg.RequiredRemoteMaxValue,
ReportShortChanID: oldCfg.ReportShortChanID,
PublishTransaction: func(txn *wire.MsgTx, _ string) error {
publishChan <- txn
return nil
@ -552,6 +610,8 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
ZombieSweeperInterval: oldCfg.ZombieSweeperInterval,
ReservationTimeout: oldCfg.ReservationTimeout,
OpenChannelPredicate: chainedAcceptor,
DeleteAliasEdge: oldCfg.DeleteAliasEdge,
AliasManager: oldCfg.AliasManager,
})
require.NoError(t, err, "failed recreating aliceFundingManager")
@ -639,7 +699,7 @@ func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
publ := fundChannel(
t, alice, bob, localFundingAmt, pushAmt, false, numConfs,
updateChan, announceChan,
updateChan, announceChan, nil,
)
fundingOutPoint := &wire.OutPoint{
Hash: publ.TxHash(),
@ -652,7 +712,8 @@ func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
// transaction is confirmed on-chain. Returns the funding tx.
func fundChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
pushAmt btcutil.Amount, subtractFees bool, numConfs uint32,
updateChan chan *lnrpc.OpenStatusUpdate, announceChan bool) *wire.MsgTx {
updateChan chan *lnrpc.OpenStatusUpdate, announceChan bool,
chanType *lnwire.ChannelType) *wire.MsgTx {
// Create a funding request and start the workflow.
errChan := make(chan error, 1)
@ -665,6 +726,7 @@ func fundChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
PushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
FundingFeePerKw: 1000,
Private: !announceChan,
ChannelType: chanType,
Updates: updateChan,
Err: errChan,
}
@ -3231,7 +3293,7 @@ func TestFundingManagerFundAll(t *testing.T) {
pushAmt := btcutil.Amount(0)
fundingTx := fundChannel(
t, alice, bob, test.spendAmt, pushAmt, true, 1,
updateChan, true,
updateChan, true, nil,
)
// Check whether the expected change output is present.
@ -3662,3 +3724,128 @@ func testUpfrontFailure(t *testing.T, pkscript []byte, expectErr bool) {
require.True(t, ok, "did not receive AcceptChannel")
}
}
// TestFundingManagerZeroConf tests that the fundingmanager properly handles
// the whole flow for zero-conf channels.
func TestFundingManagerZeroConf(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
defer tearDownFundingManagers(t, alice, bob)
// Alice and Bob will have the same set of feature bits in our test.
featureBits := []lnwire.FeatureBit{
lnwire.ZeroConfOptional,
lnwire.ScidAliasOptional,
lnwire.ExplicitChannelTypeOptional,
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
}
alice.localFeatures = featureBits
alice.remoteFeatures = featureBits
bob.localFeatures = featureBits
bob.remoteFeatures = featureBits
fundingAmt := btcutil.Amount(500000)
pushAmt := btcutil.Amount(0)
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Construct the zero-conf ChannelType for use in open_channel.
channelTypeBits := []lnwire.FeatureBit{
lnwire.ZeroConfRequired,
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
}
channelType := lnwire.ChannelType(
*lnwire.NewRawFeatureVector(channelTypeBits...),
)
// Call fundChannel with the zero-conf ChannelType.
fundingTx := fundChannel(
t, alice, bob, fundingAmt, pushAmt, false, 1, updateChan, true,
&channelType,
)
fundingOp := &wire.OutPoint{
Hash: fundingTx.TxHash(),
Index: 0,
}
// Assert that Bob's funding_locked message has an AliasScid.
bobFundingLocked := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
require.NotNil(t, bobFundingLocked.AliasScid)
require.Equal(t, *bobFundingLocked.AliasScid, alias)
// Do the same for Alice as well.
aliceFundingLocked := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
require.NotNil(t, aliceFundingLocked.AliasScid)
require.Equal(t, *aliceFundingLocked.AliasScid, alias)
// Exchange the funding_locked messages.
alice.fundingMgr.ProcessFundingMsg(bobFundingLocked, bob)
bob.fundingMgr.ProcessFundingMsg(aliceFundingLocked, alice)
// We'll assert that they both create new links.
assertHandleFundingLocked(t, alice, bob)
// We'll now assert that both sides send ChannelAnnouncement and
// ChannelUpdate messages.
assertChannelAnnouncements(t, alice, bob, fundingAmt, nil, nil)
// We'll now wait for the OpenStatusUpdate_ChanOpen update.
waitForOpenUpdate(t, updateChan)
// Assert that both Alice & Bob are in the addedToRouterGraph state.
assertAddedToRouterGraph(t, alice, bob, fundingOp)
// We'll now restart Alice's funding manager and assert that the tx
// is rebroadcast.
recreateAliceFundingManager(t, alice)
select {
case <-alice.publTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("timed out waiting for alice to rebroadcast tx")
}
// We'll now confirm the funding transaction.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
assertChannelAnnouncements(t, alice, bob, fundingAmt, nil, nil)
// Both Alice and Bob should send on reportScidChan.
select {
case <-alice.reportScidChan:
case <-time.After(time.Second * 5):
t.Fatalf("did not call ReportShortChanID in time")
}
select {
case <-bob.reportScidChan:
case <-time.After(time.Second * 5):
t.Fatalf("did not call ReportShortChanID in time")
}
// Send along the 6-confirmation channel so that announcement sigs can
// be exchanged.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
assertAnnouncementSignatures(t, alice, bob)
// Assert that the channel state is deleted from the fundingmanager's
// datastore.
assertNoChannelState(t, alice, bob, fundingOp)
}

File diff suppressed because it is too large Load Diff

View File

@ -979,6 +979,13 @@ message ChannelAcceptResponse {
The number of confirmations we require before we consider the channel open.
*/
uint32 min_accept_depth = 10;
/*
Whether the responder wants this to be a zero-conf channel. This will fail
if either side does not have the scid-alias feature bit set. The minimum
depth field must be zero if this is true.
*/
bool zero_conf = 11;
}
message ChannelPoint {

View File

@ -3409,6 +3409,10 @@
"type": "integer",
"format": "int64",
"description": "The number of confirmations we require before we consider the channel open."
},
"zero_conf": {
"type": "boolean",
"description": "Whether the responder wants this to be a zero-conf channel. This will fail\nif either side does not have the scid-alias feature bit set. The minimum\ndepth field must be zero if this is true."
}
}
},

View File

@ -975,7 +975,9 @@ func (r *ChannelRouter) pruneZombieChans() error {
toPrune = append(toPrune, chanID)
log.Tracef("Pruning zombie channel with ChannelID(%v)", chanID)
}
err = r.cfg.Graph.DeleteChannelEdges(r.cfg.StrictZombiePruning, toPrune...)
err = r.cfg.Graph.DeleteChannelEdges(
r.cfg.StrictZombiePruning, true, toPrune...,
)
if err != nil {
return fmt.Errorf("unable to delete zombie channels: %v", err)
}

View File

@ -2576,7 +2576,7 @@ func abandonChanFromGraph(chanGraph *channeldb.ChannelGraph,
// If the channel ID is still in the graph, then that means the channel
// is still open, so we'll now move to purge it from the graph.
return chanGraph.DeleteChannelEdges(false, chanID)
return chanGraph.DeleteChannelEdges(false, true, chanID)
}
// abandonChan removes a channel from the database, graph and contract court.

View File

@ -1173,6 +1173,46 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
return nil, err
}
// Wrap the DeleteChannelEdges method so that the funding manager can
// use it without depending on several layers of indirection.
deleteAliasEdge := func(scid lnwire.ShortChannelID) (
*channeldb.ChannelEdgePolicy, error) {
info, e1, e2, err := s.graphDB.FetchChannelEdgesByID(
scid.ToUint64(),
)
if err == channeldb.ErrEdgeNotFound {
// This is unlikely but there is a slim chance of this
// being hit if lnd was killed via SIGKILL and the
// funding manager was stepping through the delete
// alias edge logic.
return nil, nil
} else if err != nil {
return nil, err
}
// Grab our key to find our policy.
var ourKey [33]byte
copy(ourKey[:], nodeKeyDesc.PubKey.SerializeCompressed())
var ourPolicy *channeldb.ChannelEdgePolicy
if info != nil && info.NodeKey1Bytes == ourKey {
ourPolicy = e1
} else {
ourPolicy = e2
}
if ourPolicy == nil {
// Something is wrong, so return an error.
return nil, fmt.Errorf("we don't have an edge")
}
err = s.graphDB.DeleteChannelEdges(
false, false, scid.ToUint64(),
)
return ourPolicy, err
}
s.fundingMgr, err = funding.NewFundingManager(funding.Config{
NoWumboChans: !cfg.ProtocolOptions.Wumbo(),
IDKey: nodeKeyDesc.PubKey,
@ -1191,15 +1231,16 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
SendAnnouncement: s.authGossiper.ProcessLocalAnnouncement,
NotifyWhenOnline: s.NotifyWhenOnline,
TempChanIDSeed: chanIDSeed,
FindChannel: func(chanID lnwire.ChannelID) (
*channeldb.OpenChannel, error) {
FindChannel: func(node *btcec.PublicKey,
chanID lnwire.ChannelID) (*channeldb.OpenChannel,
error) {
dbChannels, err := s.chanStateDB.FetchAllChannels()
nodeChans, err := s.chanStateDB.FetchOpenChannels(node)
if err != nil {
return nil, err
}
for _, channel := range dbChannels {
for _, channel := range nodeChans {
if chanID.IsChanPoint(&channel.FundingOutpoint) {
return channel, nil
}
@ -1357,6 +1398,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
RegisteredChains: cfg.registeredChains,
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(
s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(),
DeleteAliasEdge: deleteAliasEdge,
AliasManager: s.aliasMgr,
})
if err != nil {
return nil, err