mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
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:
parent
13c15e8038
commit
1aa9626606
@ -184,13 +184,15 @@ func TestMultipleAcceptClients(t *testing.T) {
|
|||||||
queries = map[*lnwire.OpenChannel]*ChannelAcceptResponse{
|
queries = map[*lnwire.OpenChannel]*ChannelAcceptResponse{
|
||||||
chan1: NewChannelAcceptResponse(
|
chan1: NewChannelAcceptResponse(
|
||||||
true, nil, testUpfront, 1, 2, 3, 4, 5, 6,
|
true, nil, testUpfront, 1, 2, 3, 4, 5, 6,
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
chan2: NewChannelAcceptResponse(
|
chan2: NewChannelAcceptResponse(
|
||||||
false, errChannelRejected, nil, 0, 0, 0,
|
false, errChannelRejected, nil, 0, 0, 0,
|
||||||
0, 0, 0,
|
0, 0, 0, false,
|
||||||
),
|
),
|
||||||
chan3: NewChannelAcceptResponse(
|
chan3: NewChannelAcceptResponse(
|
||||||
false, customError, nil, 0, 0, 0, 0, 0, 0,
|
false, customError, nil, 0, 0, 0, 0, 0, 0,
|
||||||
|
false,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +247,7 @@ func TestInvalidResponse(t *testing.T) {
|
|||||||
PendingChannelID: chan1,
|
PendingChannelID: chan1,
|
||||||
}: NewChannelAcceptResponse(
|
}: NewChannelAcceptResponse(
|
||||||
false, errChannelRejected, nil, 0, 0,
|
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,
|
DustLimit: dustLimit,
|
||||||
}: NewChannelAcceptResponse(
|
}: NewChannelAcceptResponse(
|
||||||
false, errChannelRejected, nil, 0, 0,
|
false, errChannelRejected, nil, 0, 0,
|
||||||
0, reserve, 0, 0,
|
0, reserve, 0, 0, false,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ func (c *ChainedAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptRespon
|
|||||||
|
|
||||||
return NewChannelAcceptResponse(
|
return NewChannelAcceptResponse(
|
||||||
false, errChannelRejected, nil, 0, 0,
|
false, errChannelRejected, nil, 0, 0,
|
||||||
0, 0, 0, 0,
|
0, 0, 0, 0, false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,10 @@ type ChannelAcceptResponse struct {
|
|||||||
// MinAcceptDepth is the minimum depth that the initiator of the
|
// MinAcceptDepth is the minimum depth that the initiator of the
|
||||||
// channel should wait before considering the channel open.
|
// channel should wait before considering the channel open.
|
||||||
MinAcceptDepth uint16
|
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,
|
// NewChannelAcceptResponse is a constructor for a channel accept response,
|
||||||
@ -72,7 +76,7 @@ type ChannelAcceptResponse struct {
|
|||||||
func NewChannelAcceptResponse(accept bool, acceptErr error,
|
func NewChannelAcceptResponse(accept bool, acceptErr error,
|
||||||
upfrontShutdown lnwire.DeliveryAddress, csvDelay, htlcLimit,
|
upfrontShutdown lnwire.DeliveryAddress, csvDelay, htlcLimit,
|
||||||
minDepth uint16, reserve btcutil.Amount, inFlight,
|
minDepth uint16, reserve btcutil.Amount, inFlight,
|
||||||
minHtlcIn lnwire.MilliSatoshi) *ChannelAcceptResponse {
|
minHtlcIn lnwire.MilliSatoshi, zeroConf bool) *ChannelAcceptResponse {
|
||||||
|
|
||||||
resp := &ChannelAcceptResponse{
|
resp := &ChannelAcceptResponse{
|
||||||
UpfrontShutdown: upfrontShutdown,
|
UpfrontShutdown: upfrontShutdown,
|
||||||
@ -82,6 +86,7 @@ func NewChannelAcceptResponse(accept bool, acceptErr error,
|
|||||||
HtlcLimit: htlcLimit,
|
HtlcLimit: htlcLimit,
|
||||||
MinHtlcIn: minHtlcIn,
|
MinHtlcIn: minHtlcIn,
|
||||||
MinAcceptDepth: minDepth,
|
MinAcceptDepth: minDepth,
|
||||||
|
ZeroConf: zeroConf,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we want to accept the channel, we return a response with a nil
|
// If we want to accept the channel, we return a response with a nil
|
||||||
|
@ -20,6 +20,10 @@ const (
|
|||||||
fieldUpfrontShutdown = "upfront shutdown"
|
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
|
// fieldMismatchError returns a merge error for a named field when we get two
|
||||||
// channel acceptor responses which have different values set.
|
// channel acceptor responses which have different values set.
|
||||||
func fieldMismatchError(name string, current, newValue interface{}) error {
|
func fieldMismatchError(name string, current, newValue interface{}) error {
|
||||||
@ -27,6 +31,13 @@ func fieldMismatchError(name string, current, newValue interface{}) error {
|
|||||||
name, current, newValue)
|
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
|
// mergeInt64 merges two int64 values, failing if they have different non-zero
|
||||||
// values.
|
// values.
|
||||||
func mergeInt64(name string, current, newValue int64) (int64, error) {
|
func mergeInt64(name string, current, newValue int64) (int64, error) {
|
||||||
@ -117,6 +128,13 @@ func mergeResponse(current,
|
|||||||
}
|
}
|
||||||
current.MinAcceptDepth = uint16(minDepth)
|
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(
|
reserve, err := mergeInt64(
|
||||||
fieldReserve, int64(current.Reserve), int64(newValue.Reserve),
|
fieldReserve, int64(current.Reserve), int64(newValue.Reserve),
|
||||||
)
|
)
|
||||||
|
@ -167,6 +167,18 @@ func TestMergeResponse(t *testing.T) {
|
|||||||
},
|
},
|
||||||
err: nil,
|
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 {
|
for _, test := range tests {
|
||||||
|
@ -107,7 +107,7 @@ func (r *RPCAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptResponse {
|
|||||||
// Create a rejection response which we can use for the cases where we
|
// Create a rejection response which we can use for the cases where we
|
||||||
// reject the channel.
|
// reject the channel.
|
||||||
rejectChannel := NewChannelAcceptResponse(
|
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.
|
// Send the request to the newRequests channel.
|
||||||
@ -216,6 +216,7 @@ func (r *RPCAcceptor) receiveResponses(errChan chan error,
|
|||||||
MaxHtlcCount: resp.MaxHtlcCount,
|
MaxHtlcCount: resp.MaxHtlcCount,
|
||||||
MinHtlcIn: resp.MinHtlcIn,
|
MinHtlcIn: resp.MinHtlcIn,
|
||||||
MinAcceptDepth: resp.MinAcceptDepth,
|
MinAcceptDepth: resp.MinAcceptDepth,
|
||||||
|
ZeroConf: resp.ZeroConf,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have received a decision for one of our channel
|
// 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),
|
btcutil.Amount(resp.ReserveSat),
|
||||||
lnwire.MilliSatoshi(resp.InFlightMaxMsat),
|
lnwire.MilliSatoshi(resp.InFlightMaxMsat),
|
||||||
lnwire.MilliSatoshi(resp.MinHtlcIn),
|
lnwire.MilliSatoshi(resp.MinHtlcIn),
|
||||||
|
resp.ZeroConf,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Delete the channel from the acceptRequests map.
|
// Delete the channel from the acceptRequests map.
|
||||||
|
@ -1692,8 +1692,9 @@ func (c *ChannelGraph) PruneTip() (*chainhash.Hash, uint32, error) {
|
|||||||
// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is
|
// 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
|
// 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 we require the node that failed to send the fresh update to be the one
|
||||||
// that resurrects the channel from its zombie state.
|
// that resurrects the channel from its zombie state. The markZombie bool
|
||||||
func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning bool,
|
// denotes whether or not to mark the channel as a zombie.
|
||||||
|
func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool,
|
||||||
chanIDs ...uint64) error {
|
chanIDs ...uint64) error {
|
||||||
|
|
||||||
// TODO(roasbeef): possibly delete from node bucket if node has no more
|
// 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)
|
byteOrder.PutUint64(rawChanID[:], chanID)
|
||||||
err := c.delChannelEdge(
|
err := c.delChannelEdge(
|
||||||
edges, edgeIndex, chanIndex, zombieIndex, nodes,
|
edges, edgeIndex, chanIndex, zombieIndex, nodes,
|
||||||
rawChanID[:], true, strictZombiePruning,
|
rawChanID[:], markZombie, strictZombiePruning,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -378,7 +378,7 @@ func TestEdgeInsertionDeletion(t *testing.T) {
|
|||||||
|
|
||||||
// Next, attempt to delete the edge from the database, again this
|
// Next, attempt to delete the edge from the database, again this
|
||||||
// should proceed without any issues.
|
// 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)
|
t.Fatalf("unable to delete edge: %v", err)
|
||||||
}
|
}
|
||||||
assertNoEdge(t, graph, chanID)
|
assertNoEdge(t, graph, chanID)
|
||||||
@ -398,7 +398,7 @@ func TestEdgeInsertionDeletion(t *testing.T) {
|
|||||||
|
|
||||||
// Finally, attempt to delete a (now) non-existent edge within the
|
// Finally, attempt to delete a (now) non-existent edge within the
|
||||||
// database, this should result in an error.
|
// database, this should result in an error.
|
||||||
err = graph.DeleteChannelEdges(false, chanID)
|
err = graph.DeleteChannelEdges(false, true, chanID)
|
||||||
if err != ErrEdgeNotFound {
|
if err != ErrEdgeNotFound {
|
||||||
t.Fatalf("deleting a non-existent edge should fail!")
|
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 {
|
if err := graph.AddChannelEdge(&channel); err != nil {
|
||||||
t.Fatalf("unable to create channel edge: %v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unable to mark edge zombie: %v", err)
|
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 {
|
if err := graph.AddChannelEdge(&zombieChan); err != nil {
|
||||||
t.Fatalf("unable to create channel edge: %v", err)
|
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")
|
require.NoError(t, err, "unable to delete and mark edge zombie")
|
||||||
edgeQuery = append(edgeQuery, zombieChanID.ToUint64())
|
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
|
// graph. This will make Alice be seen as a private node as it no longer
|
||||||
// has any advertised edges.
|
// has any advertised edges.
|
||||||
for _, graph := range graphs {
|
for _, graph := range graphs {
|
||||||
err := graph.DeleteChannelEdges(false, aliceBobEdge.ChannelID)
|
err := graph.DeleteChannelEdges(
|
||||||
|
false, true, aliceBobEdge.ChannelID,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to remove edge: %v", err)
|
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
|
// completely remove the edge as it is not possible for her to know of
|
||||||
// it without it being advertised.
|
// it without it being advertised.
|
||||||
for i, graph := range graphs {
|
for i, graph := range graphs {
|
||||||
err := graph.DeleteChannelEdges(false, bobCarolEdge.ChannelID)
|
err := graph.DeleteChannelEdges(
|
||||||
|
false, true, bobCarolEdge.ChannelID,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to remove edge: %v", err)
|
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.
|
// 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)
|
t.Fatalf("unable to delete channel edge: %v", err)
|
||||||
}
|
}
|
||||||
disabledChanIds, err = graph.DisabledChannelIDs()
|
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
|
// If we delete the edge and mark it as a zombie, then we should expect
|
||||||
// to see it within the index.
|
// 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")
|
require.NoError(t, err, "unable to mark edge as zombie")
|
||||||
isZombie, pubKey1, pubKey2 := graph.IsZombieEdge(edge.ChannelID)
|
isZombie, pubKey1, pubKey2 := graph.IsZombieEdge(edge.ChannelID)
|
||||||
if !isZombie {
|
if !isZombie {
|
||||||
|
@ -24,10 +24,12 @@ var (
|
|||||||
// negotiateCommitmentType negotiates the commitment type of a newly opened
|
// negotiateCommitmentType negotiates the commitment type of a newly opened
|
||||||
// channel. If a channelType is provided, explicit negotiation for said type
|
// 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.
|
// will be attempted if the set of both local and remote features support it.
|
||||||
// Otherwise, implicit negotiation will be attempted.
|
// Otherwise, implicit negotiation will be attempted. Two booleans are
|
||||||
func negotiateCommitmentType(channelType *lnwire.ChannelType,
|
// returned letting the caller know if the option-scid-alias or zero-conf
|
||||||
local, remote *lnwire.FeatureVector, mustBeExplicit bool,
|
// channel types were negotiated.
|
||||||
) (bool, *lnwire.ChannelType, lnwallet.CommitmentType, error) {
|
func negotiateCommitmentType(channelType *lnwire.ChannelType, local,
|
||||||
|
remote *lnwire.FeatureVector, mustBeExplicit bool) (bool,
|
||||||
|
*lnwire.ChannelType, lnwallet.CommitmentType, error) {
|
||||||
|
|
||||||
if channelType != nil {
|
if channelType != nil {
|
||||||
// If the peer does know explicit negotiation, let's attempt
|
// 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
|
// 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
|
// 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.
|
// error is returned if either peer does not support said channel type.
|
||||||
func explicitNegotiateCommitmentType(channelType lnwire.ChannelType,
|
func explicitNegotiateCommitmentType(channelType lnwire.ChannelType, local,
|
||||||
local, remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) {
|
remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) {
|
||||||
|
|
||||||
channelFeatures := lnwire.RawFeatureVector(channelType)
|
channelFeatures := lnwire.RawFeatureVector(channelType)
|
||||||
|
|
||||||
switch {
|
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
|
// Lease script enforcement + anchors zero fee + static remote key
|
||||||
// features only.
|
// features only.
|
||||||
case channelFeatures.OnlyContains(
|
case channelFeatures.OnlyContains(
|
||||||
|
@ -21,6 +21,8 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
|
|||||||
remoteFeatures *lnwire.RawFeatureVector
|
remoteFeatures *lnwire.RawFeatureVector
|
||||||
expectsCommitType lnwallet.CommitmentType
|
expectsCommitType lnwallet.CommitmentType
|
||||||
expectsChanType lnwire.ChannelType
|
expectsChanType lnwire.ChannelType
|
||||||
|
zeroConf bool
|
||||||
|
scidAlias bool
|
||||||
expectsErr error
|
expectsErr error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -81,6 +83,134 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
|
|||||||
),
|
),
|
||||||
expectsErr: errUnsupportedChannelType,
|
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",
|
name: "explicit anchors",
|
||||||
channelFeatures: lnwire.NewRawFeatureVector(
|
channelFeatures: lnwire.NewRawFeatureVector(
|
||||||
@ -212,16 +342,50 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
|
|||||||
*testCase.channelFeatures,
|
*testCase.channelFeatures,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
_, localChanType, localCommitType, err := negotiateCommitmentType(
|
|
||||||
|
_, lChan, lCommit, err := negotiateCommitmentType(
|
||||||
channelType, localFeatures, remoteFeatures,
|
channelType, localFeatures, remoteFeatures,
|
||||||
testCase.mustBeExplicit,
|
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)
|
require.Equal(t, testCase.expectsErr, err)
|
||||||
|
|
||||||
_, remoteChanType, remoteCommitType, err := negotiateCommitmentType(
|
_, rChan, rCommit, err := negotiateCommitmentType(
|
||||||
channelType, remoteFeatures, localFeatures,
|
channelType, remoteFeatures, localFeatures,
|
||||||
testCase.mustBeExplicit,
|
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)
|
require.Equal(t, testCase.expectsErr, err)
|
||||||
|
|
||||||
if testCase.expectsErr != nil {
|
if testCase.expectsErr != nil {
|
||||||
@ -229,20 +393,20 @@ func TestCommitmentTypeNegotiation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
require.Equal(
|
require.Equal(
|
||||||
t, testCase.expectsCommitType, localCommitType,
|
t, testCase.expectsCommitType, lCommit,
|
||||||
testCase.name,
|
testCase.name,
|
||||||
)
|
)
|
||||||
require.Equal(
|
require.Equal(
|
||||||
t, testCase.expectsCommitType, remoteCommitType,
|
t, testCase.expectsCommitType, rCommit,
|
||||||
testCase.name,
|
testCase.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
require.Equal(
|
require.Equal(
|
||||||
t, testCase.expectsChanType, *localChanType,
|
t, testCase.expectsChanType, *lChan,
|
||||||
testCase.name,
|
testCase.name,
|
||||||
)
|
)
|
||||||
require.Equal(
|
require.Equal(
|
||||||
t, testCase.expectsChanType, *remoteChanType,
|
t, testCase.expectsChanType, *rChan,
|
||||||
testCase.name,
|
testCase.name,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -19,3 +19,31 @@ type Controller interface {
|
|||||||
// represents a pending channel in the Controller implementation.
|
// represents a pending channel in the Controller implementation.
|
||||||
IsPendingChannel([32]byte, lnpeer.Peer) bool
|
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
@ -115,8 +115,48 @@ var (
|
|||||||
testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey}
|
testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey}
|
||||||
|
|
||||||
fundingNetParams = chainreg.BitcoinTestNetParams
|
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 {
|
type mockNotifier struct {
|
||||||
oneConfChannel chan *chainntnfs.TxConfirmation
|
oneConfChannel chan *chainntnfs.TxConfirmation
|
||||||
sixConfChannel chan *chainntnfs.TxConfirmation
|
sixConfChannel chan *chainntnfs.TxConfirmation
|
||||||
@ -199,6 +239,8 @@ type testNode struct {
|
|||||||
mockChanEvent *mockChanEvent
|
mockChanEvent *mockChanEvent
|
||||||
testDir string
|
testDir string
|
||||||
shutdownChannel chan struct{}
|
shutdownChannel chan struct{}
|
||||||
|
reportScidChan chan struct{}
|
||||||
|
localFeatures []lnwire.FeatureBit
|
||||||
remoteFeatures []lnwire.FeatureBit
|
remoteFeatures []lnwire.FeatureBit
|
||||||
|
|
||||||
remotePeer *testNode
|
remotePeer *testNode
|
||||||
@ -234,7 +276,9 @@ func (n *testNode) QuitSignal() <-chan struct{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *testNode) LocalFeatures() *lnwire.FeatureVector {
|
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 {
|
func (n *testNode) RemoteFeatures() *lnwire.FeatureVector {
|
||||||
@ -307,10 +351,13 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
epochChan: make(chan *chainntnfs.BlockEpoch, 2),
|
epochChan: make(chan *chainntnfs.BlockEpoch, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aliasMgr := &mockAliasMgr{}
|
||||||
|
|
||||||
sentMessages := make(chan lnwire.Message)
|
sentMessages := make(chan lnwire.Message)
|
||||||
sentAnnouncements := make(chan lnwire.Message)
|
sentAnnouncements := make(chan lnwire.Message)
|
||||||
publTxChan := make(chan *wire.MsgTx, 1)
|
publTxChan := make(chan *wire.MsgTx, 1)
|
||||||
shutdownChan := make(chan struct{})
|
shutdownChan := make(chan struct{})
|
||||||
|
reportScidChan := make(chan struct{})
|
||||||
|
|
||||||
wc := &mock.WalletController{
|
wc := &mock.WalletController{
|
||||||
RootKey: alicePrivKey,
|
RootKey: alicePrivKey,
|
||||||
@ -382,14 +429,16 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
return lnwire.NodeAnnouncement{}, nil
|
return lnwire.NodeAnnouncement{}, nil
|
||||||
},
|
},
|
||||||
TempChanIDSeed: chanIDSeed,
|
TempChanIDSeed: chanIDSeed,
|
||||||
FindChannel: func(chanID lnwire.ChannelID) (
|
FindChannel: func(node *btcec.PublicKey,
|
||||||
*channeldb.OpenChannel, error) {
|
chanID lnwire.ChannelID) (*channeldb.OpenChannel,
|
||||||
dbChannels, err := cdb.FetchAllChannels()
|
error) {
|
||||||
|
|
||||||
|
nodeChans, err := cdb.FetchOpenChannels(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, channel := range dbChannels {
|
for _, channel := range nodeChans {
|
||||||
if chanID.IsChanPoint(&channel.FundingOutpoint) {
|
if chanID.IsChanPoint(&channel.FundingOutpoint) {
|
||||||
return channel, nil
|
return channel, nil
|
||||||
}
|
}
|
||||||
@ -432,6 +481,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
ReportShortChanID: func(wire.OutPoint) error {
|
ReportShortChanID: func(wire.OutPoint) error {
|
||||||
|
reportScidChan <- struct{}{}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
PublishTransaction: func(txn *wire.MsgTx, _ string) error {
|
PublishTransaction: func(txn *wire.MsgTx, _ string) error {
|
||||||
@ -450,6 +500,12 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
OpenChannelPredicate: chainedAcceptor,
|
OpenChannelPredicate: chainedAcceptor,
|
||||||
NotifyPendingOpenChannelEvent: evt.NotifyPendingOpenChannelEvent,
|
NotifyPendingOpenChannelEvent: evt.NotifyPendingOpenChannelEvent,
|
||||||
RegisteredChains: chainreg.NewChainRegistry(),
|
RegisteredChains: chainreg.NewChainRegistry(),
|
||||||
|
DeleteAliasEdge: func(scid lnwire.ShortChannelID) (
|
||||||
|
*channeldb.ChannelEdgePolicy, error) {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
AliasManager: aliasMgr,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, op := range options {
|
for _, op := range options {
|
||||||
@ -473,6 +529,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
|||||||
mockChanEvent: evt,
|
mockChanEvent: evt,
|
||||||
testDir: tempTestDir,
|
testDir: tempTestDir,
|
||||||
shutdownChannel: shutdownChan,
|
shutdownChannel: shutdownChan,
|
||||||
|
reportScidChan: reportScidChan,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,6 +599,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
|
|||||||
},
|
},
|
||||||
DefaultMinHtlcIn: 5,
|
DefaultMinHtlcIn: 5,
|
||||||
RequiredRemoteMaxValue: oldCfg.RequiredRemoteMaxValue,
|
RequiredRemoteMaxValue: oldCfg.RequiredRemoteMaxValue,
|
||||||
|
ReportShortChanID: oldCfg.ReportShortChanID,
|
||||||
PublishTransaction: func(txn *wire.MsgTx, _ string) error {
|
PublishTransaction: func(txn *wire.MsgTx, _ string) error {
|
||||||
publishChan <- txn
|
publishChan <- txn
|
||||||
return nil
|
return nil
|
||||||
@ -552,6 +610,8 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
|
|||||||
ZombieSweeperInterval: oldCfg.ZombieSweeperInterval,
|
ZombieSweeperInterval: oldCfg.ZombieSweeperInterval,
|
||||||
ReservationTimeout: oldCfg.ReservationTimeout,
|
ReservationTimeout: oldCfg.ReservationTimeout,
|
||||||
OpenChannelPredicate: chainedAcceptor,
|
OpenChannelPredicate: chainedAcceptor,
|
||||||
|
DeleteAliasEdge: oldCfg.DeleteAliasEdge,
|
||||||
|
AliasManager: oldCfg.AliasManager,
|
||||||
})
|
})
|
||||||
require.NoError(t, err, "failed recreating aliceFundingManager")
|
require.NoError(t, err, "failed recreating aliceFundingManager")
|
||||||
|
|
||||||
@ -639,7 +699,7 @@ func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
|||||||
|
|
||||||
publ := fundChannel(
|
publ := fundChannel(
|
||||||
t, alice, bob, localFundingAmt, pushAmt, false, numConfs,
|
t, alice, bob, localFundingAmt, pushAmt, false, numConfs,
|
||||||
updateChan, announceChan,
|
updateChan, announceChan, nil,
|
||||||
)
|
)
|
||||||
fundingOutPoint := &wire.OutPoint{
|
fundingOutPoint := &wire.OutPoint{
|
||||||
Hash: publ.TxHash(),
|
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.
|
// transaction is confirmed on-chain. Returns the funding tx.
|
||||||
func fundChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
func fundChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
||||||
pushAmt btcutil.Amount, subtractFees bool, numConfs uint32,
|
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.
|
// Create a funding request and start the workflow.
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
@ -665,6 +726,7 @@ func fundChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
|
|||||||
PushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
|
PushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
|
||||||
FundingFeePerKw: 1000,
|
FundingFeePerKw: 1000,
|
||||||
Private: !announceChan,
|
Private: !announceChan,
|
||||||
|
ChannelType: chanType,
|
||||||
Updates: updateChan,
|
Updates: updateChan,
|
||||||
Err: errChan,
|
Err: errChan,
|
||||||
}
|
}
|
||||||
@ -3231,7 +3293,7 @@ func TestFundingManagerFundAll(t *testing.T) {
|
|||||||
pushAmt := btcutil.Amount(0)
|
pushAmt := btcutil.Amount(0)
|
||||||
fundingTx := fundChannel(
|
fundingTx := fundChannel(
|
||||||
t, alice, bob, test.spendAmt, pushAmt, true, 1,
|
t, alice, bob, test.spendAmt, pushAmt, true, 1,
|
||||||
updateChan, true,
|
updateChan, true, nil,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check whether the expected change output is present.
|
// 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")
|
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
@ -979,6 +979,13 @@ message ChannelAcceptResponse {
|
|||||||
The number of confirmations we require before we consider the channel open.
|
The number of confirmations we require before we consider the channel open.
|
||||||
*/
|
*/
|
||||||
uint32 min_accept_depth = 10;
|
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 {
|
message ChannelPoint {
|
||||||
|
@ -3409,6 +3409,10 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64",
|
"format": "int64",
|
||||||
"description": "The number of confirmations we require before we consider the channel open."
|
"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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -975,7 +975,9 @@ func (r *ChannelRouter) pruneZombieChans() error {
|
|||||||
toPrune = append(toPrune, chanID)
|
toPrune = append(toPrune, chanID)
|
||||||
log.Tracef("Pruning zombie channel with ChannelID(%v)", 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 {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to delete zombie channels: %v", err)
|
return fmt.Errorf("unable to delete zombie channels: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -2576,7 +2576,7 @@ func abandonChanFromGraph(chanGraph *channeldb.ChannelGraph,
|
|||||||
|
|
||||||
// If the channel ID is still in the graph, then that means the channel
|
// 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.
|
// 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.
|
// abandonChan removes a channel from the database, graph and contract court.
|
||||||
|
51
server.go
51
server.go
@ -1173,6 +1173,46 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
return nil, err
|
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{
|
s.fundingMgr, err = funding.NewFundingManager(funding.Config{
|
||||||
NoWumboChans: !cfg.ProtocolOptions.Wumbo(),
|
NoWumboChans: !cfg.ProtocolOptions.Wumbo(),
|
||||||
IDKey: nodeKeyDesc.PubKey,
|
IDKey: nodeKeyDesc.PubKey,
|
||||||
@ -1191,15 +1231,16 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
SendAnnouncement: s.authGossiper.ProcessLocalAnnouncement,
|
SendAnnouncement: s.authGossiper.ProcessLocalAnnouncement,
|
||||||
NotifyWhenOnline: s.NotifyWhenOnline,
|
NotifyWhenOnline: s.NotifyWhenOnline,
|
||||||
TempChanIDSeed: chanIDSeed,
|
TempChanIDSeed: chanIDSeed,
|
||||||
FindChannel: func(chanID lnwire.ChannelID) (
|
FindChannel: func(node *btcec.PublicKey,
|
||||||
*channeldb.OpenChannel, error) {
|
chanID lnwire.ChannelID) (*channeldb.OpenChannel,
|
||||||
|
error) {
|
||||||
|
|
||||||
dbChannels, err := s.chanStateDB.FetchAllChannels()
|
nodeChans, err := s.chanStateDB.FetchOpenChannels(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, channel := range dbChannels {
|
for _, channel := range nodeChans {
|
||||||
if chanID.IsChanPoint(&channel.FundingOutpoint) {
|
if chanID.IsChanPoint(&channel.FundingOutpoint) {
|
||||||
return channel, nil
|
return channel, nil
|
||||||
}
|
}
|
||||||
@ -1357,6 +1398,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
RegisteredChains: cfg.registeredChains,
|
RegisteredChains: cfg.registeredChains,
|
||||||
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(
|
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(
|
||||||
s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(),
|
s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(),
|
||||||
|
DeleteAliasEdge: deleteAliasEdge,
|
||||||
|
AliasManager: s.aliasMgr,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
Loading…
Reference in New Issue
Block a user