lnd/chanacceptor/merge.go

171 lines
4.1 KiB
Go
Raw Normal View History

package chanacceptor
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/lnwire"
)
const (
// We use field names in our errors for more readable errors. Create
// consts for them here so that we can exactly match in our unit tests.
fieldCSV = "csv delay"
fieldHtlcLimit = "htlc limit"
fieldMinDep = "min depth"
fieldReserve = "reserve"
fieldMinIn = "min htlc in"
fieldInFlightTotal = "in flight total"
fieldUpfrontShutdown = "upfront shutdown"
)
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.
2022-04-04 22:47:05 +02:00
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 {
return fmt.Errorf("multiple values set for: %v, %v and %v",
name, current, newValue)
}
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.
2022-04-04 22:47:05 +02:00
// 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) {
switch {
case current == 0:
return newValue, nil
case newValue == 0:
return current, nil
case current != newValue:
return 0, fieldMismatchError(name, current, newValue)
default:
return newValue, nil
}
}
// mergeMillisatoshi merges two msat values, failing if they have different
// non-zero values.
func mergeMillisatoshi(name string, current,
newValue lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) {
switch {
case current == 0:
return newValue, nil
case newValue == 0:
return current, nil
case current != newValue:
return 0, fieldMismatchError(name, current, newValue)
default:
return newValue, nil
}
}
// mergeDeliveryAddress merges two delivery address values, failing if they have
// different non-zero values.
func mergeDeliveryAddress(name string, current,
newValue lnwire.DeliveryAddress) (lnwire.DeliveryAddress, error) {
switch {
case current == nil:
return newValue, nil
case newValue == nil:
return current, nil
case !bytes.Equal(current, newValue):
return nil, fieldMismatchError(name, current, newValue)
default:
return newValue, nil
}
}
// mergeResponse takes two channel accept responses, and attempts to merge their
// fields, failing if any fields conflict (are non-zero and not equal). It
// returns a new response that has all the merged fields in it.
func mergeResponse(current,
newValue ChannelAcceptResponse) (ChannelAcceptResponse, error) {
csv, err := mergeInt64(
fieldCSV, int64(current.CSVDelay), int64(newValue.CSVDelay),
)
if err != nil {
return current, err
}
current.CSVDelay = uint16(csv)
htlcLimit, err := mergeInt64(
fieldHtlcLimit, int64(current.HtlcLimit),
int64(newValue.HtlcLimit),
)
if err != nil {
return current, err
}
current.HtlcLimit = uint16(htlcLimit)
minDepth, err := mergeInt64(
fieldMinDep, int64(current.MinAcceptDepth),
int64(newValue.MinAcceptDepth),
)
if err != nil {
return current, err
}
current.MinAcceptDepth = uint16(minDepth)
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.
2022-04-04 22:47:05 +02:00
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),
)
if err != nil {
return current, err
}
current.Reserve = btcutil.Amount(reserve)
current.MinHtlcIn, err = mergeMillisatoshi(
fieldMinIn, current.MinHtlcIn, newValue.MinHtlcIn,
)
if err != nil {
return current, err
}
current.InFlightTotal, err = mergeMillisatoshi(
fieldInFlightTotal, current.InFlightTotal,
newValue.InFlightTotal,
)
if err != nil {
return current, err
}
current.UpfrontShutdown, err = mergeDeliveryAddress(
fieldUpfrontShutdown, current.UpfrontShutdown,
newValue.UpfrontShutdown,
)
if err != nil {
return current, err
}
return current, nil
}