Merge pull request #6753 from hieblmi/openchannel-fee-rates
lncli: `base_fee_msat` and `fee_rate_ppm` for `openchannel`
This commit is contained in:
14 files changed
+2708
-1947
No files matched your search
+79
-7
@@ -276,6 +276,11 @@ var (
|
|||||||
// channelOpeningState for each channel that is currently in the process
|
// channelOpeningState for each channel that is currently in the process
|
||||||
// of being opened.
|
// of being opened.
|
||||||
channelOpeningStateBucket = []byte("channelOpeningState")
|
channelOpeningStateBucket = []byte("channelOpeningState")
|
||||||
|
|
||||||
|
// initialChannelFwdingPolicyBucket is the database bucket used to store
|
||||||
|
// the forwarding policy for each permanent channel that is currently
|
||||||
|
// in the process of being opened.
|
||||||
|
initialChannelFwdingPolicyBucket = []byte("initialChannelFwdingPolicy")
|
||||||
)
|
)
|
||||||
|
|
||||||
// DB is the primary datastore for the lnd daemon. The database stores
|
// DB is the primary datastore for the lnd daemon. The database stores
|
||||||
@@ -324,7 +329,9 @@ func Open(dbPath string, modifiers ...OptionModifier) (*DB, error) {
|
|||||||
|
|
||||||
// CreateWithBackend creates channeldb instance using the passed kvdb.Backend.
|
// CreateWithBackend creates channeldb instance using the passed kvdb.Backend.
|
||||||
// Any necessary schemas migrations due to updates will take place as necessary.
|
// Any necessary schemas migrations due to updates will take place as necessary.
|
||||||
func CreateWithBackend(backend kvdb.Backend, modifiers ...OptionModifier) (*DB, error) {
|
func CreateWithBackend(backend kvdb.Backend,
|
||||||
|
modifiers ...OptionModifier) (*DB, error) {
|
||||||
|
|
||||||
opts := DefaultOptions()
|
opts := DefaultOptions()
|
||||||
for _, modifier := range modifiers {
|
for _, modifier := range modifiers {
|
||||||
modifier(&opts)
|
modifier(&opts)
|
||||||
@@ -656,7 +663,9 @@ func (c *ChannelStateDB) FetchChannel(tx kvdb.RTx, chanPoint wire.OutPoint) (
|
|||||||
|
|
||||||
// The next layer down is all the chains that this node
|
// The next layer down is all the chains that this node
|
||||||
// has channels on with us.
|
// has channels on with us.
|
||||||
return nodeChanBucket.ForEach(func(chainHash, v []byte) error {
|
return nodeChanBucket.ForEach(func(chainHash,
|
||||||
|
v []byte) error {
|
||||||
|
|
||||||
// If there's a value, it's not a bucket so
|
// If there's a value, it's not a bucket so
|
||||||
// ignore it.
|
// ignore it.
|
||||||
if v != nil {
|
if v != nil {
|
||||||
@@ -1108,8 +1117,8 @@ func (c *ChannelStateDB) pruneLinkNode(openChannels []*OpenChannel,
|
|||||||
return c.linkNodeDB.DeleteLinkNode(remotePub)
|
return c.linkNodeDB.DeleteLinkNode(remotePub)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PruneLinkNodes attempts to prune all link nodes found within the database with
|
// PruneLinkNodes attempts to prune all link nodes found within the database
|
||||||
// whom we no longer have any open channels with.
|
// with whom we no longer have any open channels with.
|
||||||
func (c *ChannelStateDB) PruneLinkNodes() error {
|
func (c *ChannelStateDB) PruneLinkNodes() error {
|
||||||
allLinkNodes, err := c.linkNodeDB.FetchAllLinkNodes()
|
allLinkNodes, err := c.linkNodeDB.FetchAllLinkNodes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1290,6 +1299,64 @@ func (c *ChannelStateDB) AbandonChannel(chanPoint *wire.OutPoint,
|
|||||||
return dbChan.CloseChannel(summary, ChanStatusLocalCloseInitiator)
|
return dbChan.CloseChannel(summary, ChanStatusLocalCloseInitiator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveInitialFwdingPolicy saves the serialized forwarding policy for the
|
||||||
|
// provided permanent channel id to the initialChannelFwdingPolicyBucket.
|
||||||
|
func (c *ChannelStateDB) SaveInitialFwdingPolicy(chanID,
|
||||||
|
forwardingPolicy []byte) error {
|
||||||
|
|
||||||
|
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
|
||||||
|
bucket, err := tx.CreateTopLevelBucket(
|
||||||
|
initialChannelFwdingPolicyBucket,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bucket.Put(chanID, forwardingPolicy)
|
||||||
|
}, func() {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInitialFwdingPolicy fetches the serialized forwarding policy for the
|
||||||
|
// provided channel id from the database, or returns ErrChannelNotFound if
|
||||||
|
// a forwarding policy for this channel id is not found.
|
||||||
|
func (c *ChannelStateDB) GetInitialFwdingPolicy(chanID []byte) ([]byte, error) {
|
||||||
|
var serializedState []byte
|
||||||
|
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
|
||||||
|
bucket := tx.ReadBucket(initialChannelFwdingPolicyBucket)
|
||||||
|
if bucket == nil {
|
||||||
|
// If the bucket does not exist, it means we
|
||||||
|
// never added a channel fees to the db, so
|
||||||
|
// return ErrChannelNotFound.
|
||||||
|
return ErrChannelNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
stateBytes := bucket.Get(chanID)
|
||||||
|
if stateBytes == nil {
|
||||||
|
return ErrChannelNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedState = append(serializedState, stateBytes...)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, func() {
|
||||||
|
serializedState = nil
|
||||||
|
})
|
||||||
|
return serializedState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteInitialFwdingPolicy removes the forwarding policy for a given channel
|
||||||
|
// from the database.
|
||||||
|
func (c *ChannelStateDB) DeleteInitialFwdingPolicy(chanID []byte) error {
|
||||||
|
return kvdb.Update(c.backend, func(tx kvdb.RwTx) error {
|
||||||
|
bucket := tx.ReadWriteBucket(initialChannelFwdingPolicyBucket)
|
||||||
|
if bucket == nil {
|
||||||
|
return ErrChannelNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return bucket.Delete(chanID)
|
||||||
|
}, func() {})
|
||||||
|
}
|
||||||
|
|
||||||
// SaveChannelOpeningState saves the serialized channel state for the provided
|
// SaveChannelOpeningState saves the serialized channel state for the provided
|
||||||
// chanPoint to the channelOpeningStateBucket.
|
// chanPoint to the channelOpeningStateBucket.
|
||||||
func (c *ChannelStateDB) SaveChannelOpeningState(outPoint,
|
func (c *ChannelStateDB) SaveChannelOpeningState(outPoint,
|
||||||
@@ -1308,7 +1375,9 @@ func (c *ChannelStateDB) SaveChannelOpeningState(outPoint,
|
|||||||
// GetChannelOpeningState fetches the serialized channel state for the provided
|
// GetChannelOpeningState fetches the serialized channel state for the provided
|
||||||
// outPoint from the database, or returns ErrChannelNotFound if the channel
|
// outPoint from the database, or returns ErrChannelNotFound if the channel
|
||||||
// is not found.
|
// is not found.
|
||||||
func (c *ChannelStateDB) GetChannelOpeningState(outPoint []byte) ([]byte, error) {
|
func (c *ChannelStateDB) GetChannelOpeningState(outPoint []byte) ([]byte,
|
||||||
|
error) {
|
||||||
|
|
||||||
var serializedState []byte
|
var serializedState []byte
|
||||||
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
|
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
|
||||||
bucket := tx.ReadBucket(channelOpeningStateBucket)
|
bucket := tx.ReadBucket(channelOpeningStateBucket)
|
||||||
@@ -1392,7 +1461,8 @@ func (d *DB) syncVersions(versions []mandatoryVersion) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Applying migration #%v", migrationVersions[i])
|
log.Infof("Applying migration #%v",
|
||||||
|
migrationVersions[i])
|
||||||
|
|
||||||
if err := migration(tx); err != nil {
|
if err := migration(tx); err != nil {
|
||||||
log.Infof("Unable to apply migration #%v",
|
log.Infof("Unable to apply migration #%v",
|
||||||
@@ -1532,7 +1602,9 @@ func fetchHistoricalChanBucket(tx kvdb.RTx,
|
|||||||
if err := writeOutpoint(&chanPointBuf, outPoint); err != nil {
|
if err := writeOutpoint(&chanPointBuf, outPoint); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
chanBucket := historicalChanBucket.NestedReadBucket(chanPointBuf.Bytes())
|
chanBucket := historicalChanBucket.NestedReadBucket(
|
||||||
|
chanPointBuf.Bytes(),
|
||||||
|
)
|
||||||
if chanBucket == nil {
|
if chanBucket == nil {
|
||||||
return nil, ErrChannelNotFound
|
return nil, ErrChannelNotFound
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,19 @@ var openChannelCommand = cli.Command{
|
|||||||
Name: "local_amt",
|
Name: "local_amt",
|
||||||
Usage: "the number of satoshis the wallet should commit to the channel",
|
Usage: "the number of satoshis the wallet should commit to the channel",
|
||||||
},
|
},
|
||||||
|
cli.Uint64Flag{
|
||||||
|
Name: "base_fee_msat",
|
||||||
|
Usage: "the base fee in milli-satoshis that will " +
|
||||||
|
"be charged for each forwarded HTLC, regardless " +
|
||||||
|
"of payment size",
|
||||||
|
},
|
||||||
|
cli.Uint64Flag{
|
||||||
|
Name: "fee_rate_ppm",
|
||||||
|
Usage: "the fee rate ppm (parts per million) that " +
|
||||||
|
"will be charged proportionally based on the value of each " +
|
||||||
|
"forwarded HTLC, the lowest possible rate is 0 " +
|
||||||
|
"with a granularity of 0.000001 (millionths)",
|
||||||
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "push_amt",
|
Name: "push_amt",
|
||||||
Usage: "the number of satoshis to give the remote side " +
|
Usage: "the number of satoshis to give the remote side " +
|
||||||
@@ -328,6 +341,16 @@ func openChannel(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.IsSet("base_fee_msat") {
|
||||||
|
req.BaseFee = ctx.Uint64("base_fee_msat")
|
||||||
|
req.UseBaseFee = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.IsSet("fee_rate_ppm") {
|
||||||
|
req.FeeRate = ctx.Uint64("fee_rate_ppm")
|
||||||
|
req.UseFeeRate = true
|
||||||
|
}
|
||||||
|
|
||||||
req.Private = ctx.Bool("private")
|
req.Private = ctx.Bool("private")
|
||||||
|
|
||||||
// Parse the channel type and map it to its RPC representation.
|
// Parse the channel type and map it to its RPC representation.
|
||||||
|
|||||||
@@ -80,6 +80,11 @@ minimum version needed to build the project.
|
|||||||
`sweeper`](https://github.com/lightningnetwork/lnd/pull/6868) to allow
|
`sweeper`](https://github.com/lightningnetwork/lnd/pull/6868) to allow
|
||||||
customize sweeper batch duration.
|
customize sweeper batch duration.
|
||||||
|
|
||||||
|
* [Add `base_fee_msat` and `fee_rate_ppm` flags to
|
||||||
|
`openchannel`](https://github.com/lightningnetwork/lnd/pull/6753) requests
|
||||||
|
so that the user can specify fees during channel creation time in addition
|
||||||
|
to the default configuration.
|
||||||
|
|
||||||
## Code Health
|
## Code Health
|
||||||
|
|
||||||
* [test: use `T.TempDir` to create temporary test
|
* [test: use `T.TempDir` to create temporary test
|
||||||
|
|||||||
+206
-34
@@ -138,6 +138,9 @@ type reservationWithCtx struct {
|
|||||||
|
|
||||||
chanAmt btcutil.Amount
|
chanAmt btcutil.Amount
|
||||||
|
|
||||||
|
// forwardingPolicy is the policy provided by the initFundingMsg.
|
||||||
|
forwardingPolicy htlcswitch.ForwardingPolicy
|
||||||
|
|
||||||
// Constraints we require for the remote.
|
// Constraints we require for the remote.
|
||||||
remoteCsvDelay uint16
|
remoteCsvDelay uint16
|
||||||
remoteMinHtlc lnwire.MilliSatoshi
|
remoteMinHtlc lnwire.MilliSatoshi
|
||||||
@@ -197,6 +200,15 @@ type InitFundingMsg struct {
|
|||||||
// LocalFundingAmt is the size of the channel.
|
// LocalFundingAmt is the size of the channel.
|
||||||
LocalFundingAmt btcutil.Amount
|
LocalFundingAmt btcutil.Amount
|
||||||
|
|
||||||
|
// BaseFee is the base fee charged for routing payments regardless of the
|
||||||
|
// number of milli-satoshis sent.
|
||||||
|
BaseFee *uint64
|
||||||
|
|
||||||
|
// FeeRate is the fee rate in ppm (parts per million) that will be charged
|
||||||
|
// proportionally based on the value of each forwarded HTLC, the lowest
|
||||||
|
// possible rate is 0 with a granularity of 0.000001 (millionths).
|
||||||
|
FeeRate *uint64
|
||||||
|
|
||||||
// PushAmt is the amount pushed to the counterparty.
|
// PushAmt is the amount pushed to the counterparty.
|
||||||
PushAmt lnwire.MilliSatoshi
|
PushAmt lnwire.MilliSatoshi
|
||||||
|
|
||||||
@@ -1592,6 +1604,14 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
|
|||||||
minHtlc = acceptorResp.MinHtlcIn
|
minHtlc = acceptorResp.MinHtlcIn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are handling a FundingOpen request then we need to
|
||||||
|
// specify the default channel fees since they are not provided
|
||||||
|
// by the responder interactively.
|
||||||
|
forwardingPolicy := htlcswitch.ForwardingPolicy{
|
||||||
|
BaseFee: f.cfg.DefaultRoutingPolicy.BaseFee,
|
||||||
|
FeeRate: f.cfg.DefaultRoutingPolicy.FeeRate,
|
||||||
|
}
|
||||||
|
|
||||||
// Once the reservation has been created successfully, we add it to
|
// Once the reservation has been created successfully, we add it to
|
||||||
// this peer's map of pending reservations to track this particular
|
// this peer's map of pending reservations to track this particular
|
||||||
// reservation until either abort or completion.
|
// reservation until either abort or completion.
|
||||||
@@ -1600,16 +1620,17 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
|
|||||||
f.activeReservations[peerIDKey] = make(pendingChannels)
|
f.activeReservations[peerIDKey] = make(pendingChannels)
|
||||||
}
|
}
|
||||||
resCtx := &reservationWithCtx{
|
resCtx := &reservationWithCtx{
|
||||||
reservation: reservation,
|
reservation: reservation,
|
||||||
chanAmt: amt,
|
chanAmt: amt,
|
||||||
remoteCsvDelay: remoteCsvDelay,
|
forwardingPolicy: forwardingPolicy,
|
||||||
remoteMinHtlc: minHtlc,
|
remoteCsvDelay: remoteCsvDelay,
|
||||||
remoteMaxValue: remoteMaxValue,
|
remoteMinHtlc: minHtlc,
|
||||||
remoteMaxHtlcs: maxHtlcs,
|
remoteMaxValue: remoteMaxValue,
|
||||||
maxLocalCsv: f.cfg.MaxLocalCSVDelay,
|
remoteMaxHtlcs: maxHtlcs,
|
||||||
channelType: msg.ChannelType,
|
maxLocalCsv: f.cfg.MaxLocalCSVDelay,
|
||||||
err: make(chan error, 1),
|
channelType: msg.ChannelType,
|
||||||
peer: peer,
|
err: make(chan error, 1),
|
||||||
|
peer: peer,
|
||||||
}
|
}
|
||||||
f.activeReservations[peerIDKey][msg.PendingChannelID] = resCtx
|
f.activeReservations[peerIDKey][msg.PendingChannelID] = resCtx
|
||||||
f.resMtx.Unlock()
|
f.resMtx.Unlock()
|
||||||
@@ -2110,6 +2131,9 @@ func (f *Manager) handleFundingCreated(peer lnpeer.Peer,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get forwarding policy before deleting the reservation context.
|
||||||
|
forwardingPolicy := resCtx.forwardingPolicy
|
||||||
|
|
||||||
// The channel is marked IsPending in the database, and can be removed
|
// The channel is marked IsPending in the database, and can be removed
|
||||||
// from the set of active reservations.
|
// from the set of active reservations.
|
||||||
f.deleteReservationCtx(peerKey, msg.PendingChannelID)
|
f.deleteReservationCtx(peerKey, msg.PendingChannelID)
|
||||||
@@ -2176,6 +2200,14 @@ func (f *Manager) handleFundingCreated(peer lnpeer.Peer,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With a permanent channel id established we can save the respective
|
||||||
|
// forwarding policy in the database. In the channel announcement phase
|
||||||
|
// this forwarding policy is retrieved and applied.
|
||||||
|
err = f.saveInitialFwdingPolicy(channelID, &forwardingPolicy)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to store the forwarding policy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Now that we've sent over our final signature for this channel, we'll
|
// Now that we've sent over our final signature for this channel, we'll
|
||||||
// send it to the ChainArbitrator so it can watch for any on-chain
|
// send it to the ChainArbitrator so it can watch for any on-chain
|
||||||
// actions during this final confirmation stage.
|
// actions during this final confirmation stage.
|
||||||
@@ -2257,6 +2289,14 @@ func (f *Manager) handleFundingSigned(peer lnpeer.Peer,
|
|||||||
f.localDiscoverySignals[permChanID] = make(chan struct{})
|
f.localDiscoverySignals[permChanID] = make(chan struct{})
|
||||||
f.localDiscoveryMtx.Unlock()
|
f.localDiscoveryMtx.Unlock()
|
||||||
|
|
||||||
|
// We have to store the forwardingPolicy before the reservation context
|
||||||
|
// is deleted. The policy will then be read and applied in
|
||||||
|
// newChanAnnouncement.
|
||||||
|
err = f.saveInitialFwdingPolicy(permChanID, &resCtx.forwardingPolicy)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to store the forwarding policy: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// The remote peer has responded with a signature for our commitment
|
// The remote peer has responded with a signature for our commitment
|
||||||
// transaction. We'll verify the signature for validity, then commit
|
// transaction. We'll verify the signature for validity, then commit
|
||||||
// the state to disk as we can now open the channel.
|
// the state to disk as we can now open the channel.
|
||||||
@@ -3082,6 +3122,15 @@ func (f *Manager) annAfterSixConfs(completeChan *channeldb.OpenChannel,
|
|||||||
return fmt.Errorf("unable to send node announcement "+
|
return fmt.Errorf("unable to send node announcement "+
|
||||||
"to peer %x: %v", pubKey, err)
|
"to peer %x: %v", pubKey, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For private channels we do not announce the channel policy
|
||||||
|
// to the network but still need to delete them from the
|
||||||
|
// database.
|
||||||
|
err = f.deleteInitialFwdingPolicy(chanID)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("Could not delete channel fees "+
|
||||||
|
"for chanId %x.", chanID)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we'll wait until the funding transaction has
|
// Otherwise, we'll wait until the funding transaction has
|
||||||
// reached 6 confirmations before announcing it.
|
// reached 6 confirmations before announcing it.
|
||||||
@@ -3523,8 +3572,14 @@ func (f *Manager) newChanAnnouncement(localPubKey,
|
|||||||
if bytes.Compare(selfBytes, remoteBytes) == -1 {
|
if bytes.Compare(selfBytes, remoteBytes) == -1 {
|
||||||
copy(chanAnn.NodeID1[:], localPubKey.SerializeCompressed())
|
copy(chanAnn.NodeID1[:], localPubKey.SerializeCompressed())
|
||||||
copy(chanAnn.NodeID2[:], remotePubKey.SerializeCompressed())
|
copy(chanAnn.NodeID2[:], remotePubKey.SerializeCompressed())
|
||||||
copy(chanAnn.BitcoinKey1[:], localFundingKey.PubKey.SerializeCompressed())
|
copy(
|
||||||
copy(chanAnn.BitcoinKey2[:], remoteFundingKey.SerializeCompressed())
|
chanAnn.BitcoinKey1[:],
|
||||||
|
localFundingKey.PubKey.SerializeCompressed(),
|
||||||
|
)
|
||||||
|
copy(
|
||||||
|
chanAnn.BitcoinKey2[:],
|
||||||
|
remoteFundingKey.SerializeCompressed(),
|
||||||
|
)
|
||||||
|
|
||||||
// If we're the first node then update the chanFlags to
|
// If we're the first node then update the chanFlags to
|
||||||
// indicate the "direction" of the update.
|
// indicate the "direction" of the update.
|
||||||
@@ -3532,8 +3587,14 @@ func (f *Manager) newChanAnnouncement(localPubKey,
|
|||||||
} else {
|
} else {
|
||||||
copy(chanAnn.NodeID1[:], remotePubKey.SerializeCompressed())
|
copy(chanAnn.NodeID1[:], remotePubKey.SerializeCompressed())
|
||||||
copy(chanAnn.NodeID2[:], localPubKey.SerializeCompressed())
|
copy(chanAnn.NodeID2[:], localPubKey.SerializeCompressed())
|
||||||
copy(chanAnn.BitcoinKey1[:], remoteFundingKey.SerializeCompressed())
|
copy(
|
||||||
copy(chanAnn.BitcoinKey2[:], localFundingKey.PubKey.SerializeCompressed())
|
chanAnn.BitcoinKey1[:],
|
||||||
|
remoteFundingKey.SerializeCompressed(),
|
||||||
|
)
|
||||||
|
copy(
|
||||||
|
chanAnn.BitcoinKey2[:],
|
||||||
|
localFundingKey.PubKey.SerializeCompressed(),
|
||||||
|
)
|
||||||
|
|
||||||
// If we're the second node then update the chanFlags to
|
// If we're the second node then update the chanFlags to
|
||||||
// indicate the "direction" of the update.
|
// indicate the "direction" of the update.
|
||||||
@@ -3552,19 +3613,24 @@ func (f *Manager) newChanAnnouncement(localPubKey,
|
|||||||
Timestamp: uint32(time.Now().Unix()),
|
Timestamp: uint32(time.Now().Unix()),
|
||||||
MessageFlags: msgFlags,
|
MessageFlags: msgFlags,
|
||||||
ChannelFlags: chanFlags,
|
ChannelFlags: chanFlags,
|
||||||
TimeLockDelta: uint16(f.cfg.DefaultRoutingPolicy.TimeLockDelta),
|
TimeLockDelta: uint16(
|
||||||
|
f.cfg.DefaultRoutingPolicy.TimeLockDelta,
|
||||||
// We use the HtlcMinimumMsat that the remote party required us
|
),
|
||||||
// to use, as our ChannelUpdate will be used to carry HTLCs
|
|
||||||
// towards them.
|
|
||||||
HtlcMinimumMsat: fwdMinHTLC,
|
HtlcMinimumMsat: fwdMinHTLC,
|
||||||
HtlcMaximumMsat: fwdMaxHTLC,
|
HtlcMaximumMsat: fwdMaxHTLC,
|
||||||
|
|
||||||
BaseFee: uint32(f.cfg.DefaultRoutingPolicy.BaseFee),
|
|
||||||
FeeRate: uint32(f.cfg.DefaultRoutingPolicy.FeeRate),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ourPolicy != nil {
|
// The caller of newChanAnnouncement is expected to provide the initial
|
||||||
|
// forwarding policy to be announced. We abort the channel announcement
|
||||||
|
// if they are not provided.
|
||||||
|
storedFwdingPolicy, err := f.getInitialFwdingPolicy(chanID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("unable to generate channel "+
|
||||||
|
"update announcement: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case ourPolicy != nil:
|
||||||
// If ourPolicy is non-nil, modify the default parameters of the
|
// If ourPolicy is non-nil, modify the default parameters of the
|
||||||
// ChannelUpdate.
|
// ChannelUpdate.
|
||||||
chanUpdateAnn.MessageFlags = ourPolicy.MessageFlags
|
chanUpdateAnn.MessageFlags = ourPolicy.MessageFlags
|
||||||
@@ -3576,6 +3642,21 @@ func (f *Manager) newChanAnnouncement(localPubKey,
|
|||||||
chanUpdateAnn.FeeRate = uint32(
|
chanUpdateAnn.FeeRate = uint32(
|
||||||
ourPolicy.FeeProportionalMillionths,
|
ourPolicy.FeeProportionalMillionths,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
case storedFwdingPolicy != nil:
|
||||||
|
chanUpdateAnn.BaseFee = uint32(storedFwdingPolicy.BaseFee)
|
||||||
|
chanUpdateAnn.FeeRate = uint32(storedFwdingPolicy.FeeRate)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Infof("No channel forwaring policy specified for channel "+
|
||||||
|
"announcement of ChannelID(%v). "+
|
||||||
|
"Assuming default fee parameters.", chanID)
|
||||||
|
chanUpdateAnn.BaseFee = uint32(
|
||||||
|
f.cfg.DefaultRoutingPolicy.BaseFee,
|
||||||
|
)
|
||||||
|
chanUpdateAnn.FeeRate = uint32(
|
||||||
|
f.cfg.DefaultRoutingPolicy.FeeRate,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the channel update announcement constructed, we'll generate a
|
// With the channel update announcement constructed, we'll generate a
|
||||||
@@ -3672,6 +3753,14 @@ func (f *Manager) announceChannel(localIDKey, remoteIDKey *btcec.PublicKey,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After the fee parameters have been stored in the announcement
|
||||||
|
// we can delete them from the database.
|
||||||
|
err = f.deleteInitialFwdingPolicy(chanID)
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("Could not delete channel fees for chanId %x.",
|
||||||
|
chanID)
|
||||||
|
}
|
||||||
|
|
||||||
// We only send the channel proof announcement and the node announcement
|
// We only send the channel proof announcement and the node announcement
|
||||||
// because addToRouterGraph previously sent the ChannelAnnouncement and
|
// because addToRouterGraph previously sent the ChannelAnnouncement and
|
||||||
// the ChannelUpdate announcement messages. The channel proof and node
|
// the ChannelUpdate announcement messages. The channel proof and node
|
||||||
@@ -3792,6 +3881,8 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
|
|||||||
var (
|
var (
|
||||||
peerKey = msg.Peer.IdentityKey()
|
peerKey = msg.Peer.IdentityKey()
|
||||||
localAmt = msg.LocalFundingAmt
|
localAmt = msg.LocalFundingAmt
|
||||||
|
baseFee = msg.BaseFee
|
||||||
|
feeRate = msg.FeeRate
|
||||||
minHtlcIn = msg.MinHtlcIn
|
minHtlcIn = msg.MinHtlcIn
|
||||||
remoteCsvDelay = msg.RemoteCsvDelay
|
remoteCsvDelay = msg.RemoteCsvDelay
|
||||||
maxValue = msg.MaxValueInFlight
|
maxValue = msg.MaxValueInFlight
|
||||||
@@ -3985,6 +4076,22 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
|
|||||||
maxHtlcs = f.cfg.RequiredRemoteMaxHTLCs(capacity)
|
maxHtlcs = f.cfg.RequiredRemoteMaxHTLCs(capacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare the optional channel fee values from the initFundingMsg.
|
||||||
|
// If useBaseFee or useFeeRate are false the client did not
|
||||||
|
// provide fee values hence we assume default fee settings from
|
||||||
|
// the config.
|
||||||
|
forwardingPolicy := htlcswitch.ForwardingPolicy{
|
||||||
|
BaseFee: f.cfg.DefaultRoutingPolicy.BaseFee,
|
||||||
|
FeeRate: f.cfg.DefaultRoutingPolicy.FeeRate,
|
||||||
|
}
|
||||||
|
if baseFee != nil {
|
||||||
|
forwardingPolicy.BaseFee = lnwire.MilliSatoshi(*baseFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
if feeRate != nil {
|
||||||
|
forwardingPolicy.FeeRate = lnwire.MilliSatoshi(*feeRate)
|
||||||
|
}
|
||||||
|
|
||||||
// If a pending channel map for this peer isn't already created, then
|
// If a pending channel map for this peer isn't already created, then
|
||||||
// we create one, ultimately allowing us to track this pending
|
// we create one, ultimately allowing us to track this pending
|
||||||
// reservation within the target peer.
|
// reservation within the target peer.
|
||||||
@@ -3995,17 +4102,18 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resCtx := &reservationWithCtx{
|
resCtx := &reservationWithCtx{
|
||||||
chanAmt: capacity,
|
chanAmt: capacity,
|
||||||
remoteCsvDelay: remoteCsvDelay,
|
forwardingPolicy: forwardingPolicy,
|
||||||
remoteMinHtlc: minHtlcIn,
|
remoteCsvDelay: remoteCsvDelay,
|
||||||
remoteMaxValue: maxValue,
|
remoteMinHtlc: minHtlcIn,
|
||||||
remoteMaxHtlcs: maxHtlcs,
|
remoteMaxValue: maxValue,
|
||||||
maxLocalCsv: maxCSV,
|
remoteMaxHtlcs: maxHtlcs,
|
||||||
channelType: msg.ChannelType,
|
maxLocalCsv: maxCSV,
|
||||||
reservation: reservation,
|
channelType: msg.ChannelType,
|
||||||
peer: msg.Peer,
|
reservation: reservation,
|
||||||
updates: msg.Updates,
|
peer: msg.Peer,
|
||||||
err: msg.Err,
|
updates: msg.Updates,
|
||||||
|
err: msg.Err,
|
||||||
}
|
}
|
||||||
f.activeReservations[peerIDKey][chanID] = resCtx
|
f.activeReservations[peerIDKey][chanID] = resCtx
|
||||||
f.resMtx.Unlock()
|
f.resMtx.Unlock()
|
||||||
@@ -4267,6 +4375,70 @@ func copyPubKey(pub *btcec.PublicKey) *btcec.PublicKey {
|
|||||||
return btcec.NewPublicKey(&tmp.X, &tmp.Y)
|
return btcec.NewPublicKey(&tmp.X, &tmp.Y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// saveInitialFwdingPolicy saves the forwarding policy for the provided
|
||||||
|
// chanPoint in the channelOpeningStateBucket.
|
||||||
|
func (f *Manager) saveInitialFwdingPolicy(permChanID lnwire.ChannelID,
|
||||||
|
forwardingPolicy *htlcswitch.ForwardingPolicy) error {
|
||||||
|
|
||||||
|
chanID := make([]byte, 32)
|
||||||
|
copy(chanID, permChanID[:])
|
||||||
|
|
||||||
|
scratch := make([]byte, 36)
|
||||||
|
byteOrder.PutUint64(scratch[:8], uint64(forwardingPolicy.MinHTLCOut))
|
||||||
|
byteOrder.PutUint64(scratch[8:16], uint64(forwardingPolicy.MaxHTLC))
|
||||||
|
byteOrder.PutUint64(scratch[16:24], uint64(forwardingPolicy.BaseFee))
|
||||||
|
byteOrder.PutUint64(scratch[24:32], uint64(forwardingPolicy.FeeRate))
|
||||||
|
byteOrder.PutUint32(scratch[32:], forwardingPolicy.TimeLockDelta)
|
||||||
|
|
||||||
|
return f.cfg.Wallet.Cfg.Database.SaveInitialFwdingPolicy(
|
||||||
|
chanID, scratch,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInitialFwdingPolicy fetches the initial forwarding policy for a given
|
||||||
|
// channel id from the database which will be applied during the channel
|
||||||
|
// announcement phase.
|
||||||
|
func (f *Manager) getInitialFwdingPolicy(permChanID lnwire.ChannelID) (
|
||||||
|
*htlcswitch.ForwardingPolicy, error) {
|
||||||
|
|
||||||
|
chanID := make([]byte, 32)
|
||||||
|
copy(chanID, permChanID[:])
|
||||||
|
|
||||||
|
value, err := f.cfg.Wallet.Cfg.Database.GetInitialFwdingPolicy(
|
||||||
|
chanID,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fwdingPolicy htlcswitch.ForwardingPolicy
|
||||||
|
fwdingPolicy.MinHTLCOut = lnwire.MilliSatoshi(
|
||||||
|
byteOrder.Uint64(value[:8]),
|
||||||
|
)
|
||||||
|
fwdingPolicy.MaxHTLC = lnwire.MilliSatoshi(
|
||||||
|
byteOrder.Uint64(value[8:16]),
|
||||||
|
)
|
||||||
|
fwdingPolicy.BaseFee = lnwire.MilliSatoshi(
|
||||||
|
byteOrder.Uint64(value[16:24]),
|
||||||
|
)
|
||||||
|
fwdingPolicy.FeeRate = lnwire.MilliSatoshi(
|
||||||
|
byteOrder.Uint64(value[24:32]),
|
||||||
|
)
|
||||||
|
fwdingPolicy.TimeLockDelta = byteOrder.Uint32(value[32:36])
|
||||||
|
|
||||||
|
return &fwdingPolicy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteInitialFwdingPolicy removes channel fees for this chanID from
|
||||||
|
// the database.
|
||||||
|
func (f *Manager) deleteInitialFwdingPolicy(permChanID lnwire.ChannelID) error {
|
||||||
|
chanID := make([]byte, 32)
|
||||||
|
copy(chanID, permChanID[:])
|
||||||
|
|
||||||
|
return f.cfg.Wallet.Cfg.Database.DeleteInitialFwdingPolicy(chanID)
|
||||||
|
}
|
||||||
|
|
||||||
// saveChannelOpeningState saves the channelOpeningState for the provided
|
// saveChannelOpeningState saves the channelOpeningState for the provided
|
||||||
// chanPoint to the channelOpeningStateBucket.
|
// chanPoint to the channelOpeningStateBucket.
|
||||||
func (f *Manager) saveChannelOpeningState(chanPoint *wire.OutPoint,
|
func (f *Manager) saveChannelOpeningState(chanPoint *wire.OutPoint,
|
||||||
|
|||||||
+214
-17
@@ -1044,11 +1044,12 @@ func assertAddedToRouterGraph(t *testing.T, alice, bob *testNode,
|
|||||||
// confirmed. The last arguments can be set if we expect the nodes to advertise
|
// confirmed. The last arguments can be set if we expect the nodes to advertise
|
||||||
// custom min_htlc values as part of their ChannelUpdate. We expect Alice to
|
// custom min_htlc values as part of their ChannelUpdate. We expect Alice to
|
||||||
// advertise the value required by Bob and vice versa. If they are not set the
|
// advertise the value required by Bob and vice versa. If they are not set the
|
||||||
// advertised value will be checked against the other node's default min_htlc
|
// advertised value will be checked against the other node's default min_htlc,
|
||||||
// value.
|
// base fee and fee rate values.
|
||||||
func assertChannelAnnouncements(t *testing.T, alice, bob *testNode,
|
func assertChannelAnnouncements(t *testing.T, alice, bob *testNode,
|
||||||
capacity btcutil.Amount, customMinHtlc []lnwire.MilliSatoshi,
|
capacity btcutil.Amount, customMinHtlc []lnwire.MilliSatoshi,
|
||||||
customMaxHtlc []lnwire.MilliSatoshi) {
|
customMaxHtlc []lnwire.MilliSatoshi, baseFees []lnwire.MilliSatoshi,
|
||||||
|
feeRates []lnwire.MilliSatoshi) {
|
||||||
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -1127,6 +1128,46 @@ func assertChannelAnnouncements(t *testing.T, alice, bob *testNode,
|
|||||||
m.HtlcMaximumMsat)
|
m.HtlcMaximumMsat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
baseFee := alice.fundingMgr.cfg.DefaultRoutingPolicy.BaseFee
|
||||||
|
|
||||||
|
// We might expect a custom baseFee value.
|
||||||
|
if len(baseFees) > 0 {
|
||||||
|
if len(baseFees) != 2 {
|
||||||
|
t.Fatalf("only 0 or 2 custom " +
|
||||||
|
"base fee values " +
|
||||||
|
"currently supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseFee = baseFees[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint32(baseFee) != m.BaseFee {
|
||||||
|
t.Fatalf("expected ChannelUpdate to "+
|
||||||
|
"advertise base fee %v, had %v",
|
||||||
|
baseFee,
|
||||||
|
m.BaseFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
feeRate := alice.fundingMgr.cfg.DefaultRoutingPolicy.FeeRate
|
||||||
|
|
||||||
|
// We might expect a custom feeRate value.
|
||||||
|
if len(feeRates) > 0 {
|
||||||
|
if len(feeRates) != 2 {
|
||||||
|
t.Fatalf("only 0 or 2 custom " +
|
||||||
|
"fee rate values " +
|
||||||
|
"currently supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
feeRate = feeRates[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint32(feeRate) != m.FeeRate {
|
||||||
|
t.Fatalf("expected ChannelUpdate to "+
|
||||||
|
"advertise base fee %v, had %v",
|
||||||
|
feeRate,
|
||||||
|
m.FeeRate)
|
||||||
|
}
|
||||||
|
|
||||||
gotChannelUpdate = true
|
gotChannelUpdate = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1214,6 +1255,16 @@ func assertNoChannelState(t *testing.T, alice, bob *testNode,
|
|||||||
assertErrChannelNotFound(t, bob, fundingOutPoint)
|
assertErrChannelNotFound(t, bob, fundingOutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertNoFwdingPolicy(t *testing.T, alice, bob *testNode,
|
||||||
|
fundingOutPoint *wire.OutPoint) {
|
||||||
|
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
chandID := lnwire.NewChanIDFromOutPoint(fundingOutPoint)
|
||||||
|
assertInitialFwdingPolicyNotFound(t, alice, &chandID)
|
||||||
|
assertInitialFwdingPolicyNotFound(t, bob, &chandID)
|
||||||
|
}
|
||||||
|
|
||||||
func assertErrChannelNotFound(t *testing.T, node *testNode,
|
func assertErrChannelNotFound(t *testing.T, node *testNode,
|
||||||
fundingOutPoint *wire.OutPoint) {
|
fundingOutPoint *wire.OutPoint) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -1239,6 +1290,33 @@ func assertErrChannelNotFound(t *testing.T, node *testNode,
|
|||||||
t.Fatalf("expected to not find state, found state %v", state)
|
t.Fatalf("expected to not find state, found state %v", state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertInitialFwdingPolicyNotFound(t *testing.T, node *testNode,
|
||||||
|
chanID *lnwire.ChannelID) {
|
||||||
|
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var fwdingPolicy *htlcswitch.ForwardingPolicy
|
||||||
|
var err error
|
||||||
|
for i := 0; i < testPollNumTries; i++ {
|
||||||
|
// If this is not the first try, sleep before retrying.
|
||||||
|
if i > 0 {
|
||||||
|
time.Sleep(testPollSleepMs * time.Millisecond)
|
||||||
|
}
|
||||||
|
fwdingPolicy, err = node.fundingMgr.getInitialFwdingPolicy(
|
||||||
|
*chanID)
|
||||||
|
if err == channeldb.ErrChannelNotFound {
|
||||||
|
// Got expected result, return with success.
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatalf("unable to get forwarding policy from db: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10 tries without success.
|
||||||
|
t.Fatalf("expected to not find a forwarding policy, found policy %v",
|
||||||
|
fwdingPolicy)
|
||||||
|
}
|
||||||
|
|
||||||
func assertHandleFundingLocked(t *testing.T, alice, bob *testNode) {
|
func assertHandleFundingLocked(t *testing.T, alice, bob *testNode) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -1317,7 +1395,7 @@ func TestFundingManagerNormalWorkflow(t *testing.T) {
|
|||||||
|
|
||||||
// Make sure both fundingManagers send the expected channel
|
// Make sure both fundingManagers send the expected channel
|
||||||
// announcements.
|
// announcements.
|
||||||
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil)
|
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
|
||||||
|
|
||||||
// Check that the state machine is updated accordingly
|
// Check that the state machine is updated accordingly
|
||||||
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
||||||
@@ -1340,6 +1418,10 @@ func TestFundingManagerNormalWorkflow(t *testing.T) {
|
|||||||
// The internal state-machine should now have deleted the channelStates
|
// The internal state-machine should now have deleted the channelStates
|
||||||
// from the database, as the channel is announced.
|
// from the database, as the channel is announced.
|
||||||
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
||||||
|
|
||||||
|
// The forwarding policy for the channel announcement should
|
||||||
|
// have been deleted from the database, as the channel is announced.
|
||||||
|
assertNoFwdingPolicy(t, alice, bob, fundingOutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFundingManagerRejectCSV tests checking of local CSV values against our
|
// TestFundingManagerRejectCSV tests checking of local CSV values against our
|
||||||
@@ -1526,10 +1608,10 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
|
|||||||
bob.sendMessage = func(msg lnwire.Message) error {
|
bob.sendMessage = func(msg lnwire.Message) error {
|
||||||
return fmt.Errorf("intentional error in SendToPeer")
|
return fmt.Errorf("intentional error in SendToPeer")
|
||||||
}
|
}
|
||||||
alice.fundingMgr.cfg.NotifyWhenOnline = func(peer [33]byte,
|
notifyWhenOnline := func(peer [33]byte, con chan<- lnpeer.Peer) {
|
||||||
con chan<- lnpeer.Peer) {
|
// Intentionally empty
|
||||||
// Intentionally empty.
|
|
||||||
}
|
}
|
||||||
|
alice.fundingMgr.cfg.NotifyWhenOnline = notifyWhenOnline
|
||||||
|
|
||||||
// Notify that transaction was mined
|
// Notify that transaction was mined
|
||||||
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
|
||||||
@@ -1616,7 +1698,7 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
|
|||||||
|
|
||||||
// Make sure both fundingManagers send the expected channel
|
// Make sure both fundingManagers send the expected channel
|
||||||
// announcements.
|
// announcements.
|
||||||
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil)
|
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
|
||||||
|
|
||||||
// Check that the state machine is updated accordingly
|
// Check that the state machine is updated accordingly
|
||||||
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
||||||
@@ -1641,6 +1723,10 @@ func TestFundingManagerRestartBehavior(t *testing.T) {
|
|||||||
// The internal state-machine should now have deleted the channelStates
|
// The internal state-machine should now have deleted the channelStates
|
||||||
// from the database, as the channel is announced.
|
// from the database, as the channel is announced.
|
||||||
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
||||||
|
|
||||||
|
// The forwarding policy for the channel announcement should
|
||||||
|
// have been deleted from the database, as the channel is announced.
|
||||||
|
assertNoFwdingPolicy(t, alice, bob, fundingOutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFundingManagerOfflinePeer checks that the fundingManager waits for the
|
// TestFundingManagerOfflinePeer checks that the fundingManager waits for the
|
||||||
@@ -1771,7 +1857,7 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
|
|||||||
|
|
||||||
// Make sure both fundingManagers send the expected channel
|
// Make sure both fundingManagers send the expected channel
|
||||||
// announcements.
|
// announcements.
|
||||||
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil)
|
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
|
||||||
|
|
||||||
// Check that the state machine is updated accordingly
|
// Check that the state machine is updated accordingly
|
||||||
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
||||||
@@ -1795,6 +1881,10 @@ func TestFundingManagerOfflinePeer(t *testing.T) {
|
|||||||
// The internal state-machine should now have deleted the channelStates
|
// The internal state-machine should now have deleted the channelStates
|
||||||
// from the database, as the channel is announced.
|
// from the database, as the channel is announced.
|
||||||
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
||||||
|
|
||||||
|
// The forwarding policy for the channel announcement should
|
||||||
|
// have been deleted from the database, as the channel is announced.
|
||||||
|
assertNoFwdingPolicy(t, alice, bob, fundingOutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFundingManagerPeerTimeoutAfterInitFunding checks that the zombie sweeper
|
// TestFundingManagerPeerTimeoutAfterInitFunding checks that the zombie sweeper
|
||||||
@@ -2198,7 +2288,7 @@ func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) {
|
|||||||
|
|
||||||
// Make sure both fundingManagers send the expected channel
|
// Make sure both fundingManagers send the expected channel
|
||||||
// announcements.
|
// announcements.
|
||||||
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil)
|
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
|
||||||
|
|
||||||
// Check that the state machine is updated accordingly
|
// Check that the state machine is updated accordingly
|
||||||
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
||||||
@@ -2221,6 +2311,10 @@ func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) {
|
|||||||
// The internal state-machine should now have deleted the channelStates
|
// The internal state-machine should now have deleted the channelStates
|
||||||
// from the database, as the channel is announced.
|
// from the database, as the channel is announced.
|
||||||
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
||||||
|
|
||||||
|
// The forwarding policy for the channel announcement should
|
||||||
|
// have been deleted from the database, as the channel is announced.
|
||||||
|
assertNoFwdingPolicy(t, alice, bob, fundingOutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFundingManagerRestartAfterChanAnn checks that the fundingManager properly
|
// TestFundingManagerRestartAfterChanAnn checks that the fundingManager properly
|
||||||
@@ -2281,7 +2375,7 @@ func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
|
|||||||
|
|
||||||
// Make sure both fundingManagers send the expected channel
|
// Make sure both fundingManagers send the expected channel
|
||||||
// announcements.
|
// announcements.
|
||||||
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil)
|
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
|
||||||
|
|
||||||
// Check that the state machine is updated accordingly
|
// Check that the state machine is updated accordingly
|
||||||
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
||||||
@@ -2309,6 +2403,10 @@ func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
|
|||||||
// The internal state-machine should now have deleted the channelStates
|
// The internal state-machine should now have deleted the channelStates
|
||||||
// from the database, as the channel is announced.
|
// from the database, as the channel is announced.
|
||||||
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
||||||
|
|
||||||
|
// The forwarding policy for the channel announcement should
|
||||||
|
// have been deleted from the database, as the channel is announced.
|
||||||
|
assertNoFwdingPolicy(t, alice, bob, fundingOutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFundingManagerRestartAfterReceivingFundingLocked checks that the
|
// TestFundingManagerRestartAfterReceivingFundingLocked checks that the
|
||||||
@@ -2374,7 +2472,7 @@ func TestFundingManagerRestartAfterReceivingFundingLocked(t *testing.T) {
|
|||||||
|
|
||||||
// Make sure both fundingManagers send the expected channel
|
// Make sure both fundingManagers send the expected channel
|
||||||
// announcements.
|
// announcements.
|
||||||
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil)
|
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
|
||||||
|
|
||||||
// Check that the state machine is updated accordingly
|
// Check that the state machine is updated accordingly
|
||||||
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
|
||||||
@@ -2393,6 +2491,10 @@ func TestFundingManagerRestartAfterReceivingFundingLocked(t *testing.T) {
|
|||||||
// The internal state-machine should now have deleted the channelStates
|
// The internal state-machine should now have deleted the channelStates
|
||||||
// from the database, as the channel is announced.
|
// from the database, as the channel is announced.
|
||||||
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
||||||
|
|
||||||
|
// The forwarding policy for the channel announcement should
|
||||||
|
// have been deleted from the database, as the channel is announced.
|
||||||
|
assertNoFwdingPolicy(t, alice, bob, fundingOutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFundingManagerPrivateChannel tests that if we open a private channel
|
// TestFundingManagerPrivateChannel tests that if we open a private channel
|
||||||
@@ -2453,7 +2555,7 @@ func TestFundingManagerPrivateChannel(t *testing.T) {
|
|||||||
|
|
||||||
// Make sure both fundingManagers send the expected channel
|
// Make sure both fundingManagers send the expected channel
|
||||||
// announcements.
|
// announcements.
|
||||||
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil)
|
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
|
||||||
|
|
||||||
// The funding transaction is now confirmed, wait for the
|
// The funding transaction is now confirmed, wait for the
|
||||||
// OpenStatusUpdate_ChanOpen update
|
// OpenStatusUpdate_ChanOpen update
|
||||||
@@ -2505,6 +2607,10 @@ func TestFundingManagerPrivateChannel(t *testing.T) {
|
|||||||
// The internal state-machine should now have deleted the channelStates
|
// The internal state-machine should now have deleted the channelStates
|
||||||
// from the database, as the channel is announced.
|
// from the database, as the channel is announced.
|
||||||
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
||||||
|
|
||||||
|
// The forwarding policy for the channel announcement should
|
||||||
|
// have been deleted from the database.
|
||||||
|
assertNoFwdingPolicy(t, alice, bob, fundingOutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFundingManagerPrivateRestart tests that the privacy guarantees granted
|
// TestFundingManagerPrivateRestart tests that the privacy guarantees granted
|
||||||
@@ -2566,7 +2672,7 @@ func TestFundingManagerPrivateRestart(t *testing.T) {
|
|||||||
|
|
||||||
// Make sure both fundingManagers send the expected channel
|
// Make sure both fundingManagers send the expected channel
|
||||||
// announcements.
|
// announcements.
|
||||||
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil)
|
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
|
||||||
|
|
||||||
// Note: We don't check for the addedToRouterGraph state because in
|
// Note: We don't check for the addedToRouterGraph state because in
|
||||||
// the private channel mode, the state is quickly changed from
|
// the private channel mode, the state is quickly changed from
|
||||||
@@ -2640,6 +2746,10 @@ func TestFundingManagerPrivateRestart(t *testing.T) {
|
|||||||
// The internal state-machine should now have deleted the channelStates
|
// The internal state-machine should now have deleted the channelStates
|
||||||
// from the database, as the channel is announced.
|
// from the database, as the channel is announced.
|
||||||
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
assertNoChannelState(t, alice, bob, fundingOutPoint)
|
||||||
|
|
||||||
|
// The forwarding policy for the channel announcement should
|
||||||
|
// have been deleted from the database.
|
||||||
|
assertNoFwdingPolicy(t, alice, bob, fundingOutPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFundingManagerCustomChannelParameters checks that custom requirements we
|
// TestFundingManagerCustomChannelParameters checks that custom requirements we
|
||||||
@@ -2656,6 +2766,13 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
|||||||
const maxValueInFlight = 50000
|
const maxValueInFlight = 50000
|
||||||
const fundingAmt = 5000000
|
const fundingAmt = 5000000
|
||||||
|
|
||||||
|
// Use custom channel fees.
|
||||||
|
// These will show up in the channel reservation context
|
||||||
|
var baseFee uint64
|
||||||
|
var feeRate uint64
|
||||||
|
baseFee = 42
|
||||||
|
feeRate = 1337
|
||||||
|
|
||||||
// We will consume the channel updates as we go, so no buffering is
|
// We will consume the channel updates as we go, so no buffering is
|
||||||
// needed.
|
// needed.
|
||||||
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
updateChan := make(chan *lnrpc.OpenStatusUpdate)
|
||||||
@@ -2679,6 +2796,8 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
|||||||
RemoteCsvDelay: csvDelay,
|
RemoteCsvDelay: csvDelay,
|
||||||
Updates: updateChan,
|
Updates: updateChan,
|
||||||
Err: errChan,
|
Err: errChan,
|
||||||
|
BaseFee: &baseFee,
|
||||||
|
FeeRate: &feeRate,
|
||||||
}
|
}
|
||||||
|
|
||||||
alice.fundingMgr.InitFundingWorkflow(initReq)
|
alice.fundingMgr.InitFundingWorkflow(initReq)
|
||||||
@@ -2819,6 +2938,23 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper method for checking baseFee and feeRate stored for a
|
||||||
|
// reservation.
|
||||||
|
assertFees := func(forwardingPolicy *htlcswitch.ForwardingPolicy,
|
||||||
|
baseFee, feeRate lnwire.MilliSatoshi) error {
|
||||||
|
|
||||||
|
if forwardingPolicy.BaseFee != baseFee {
|
||||||
|
return fmt.Errorf("expected baseFee to be %v, "+
|
||||||
|
"was %v", baseFee, forwardingPolicy.BaseFee)
|
||||||
|
}
|
||||||
|
|
||||||
|
if forwardingPolicy.FeeRate != feeRate {
|
||||||
|
return fmt.Errorf("expected feeRate to be %v, "+
|
||||||
|
"was %v", feeRate, forwardingPolicy.FeeRate)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Check that the custom channel parameters were properly set in the
|
// Check that the custom channel parameters were properly set in the
|
||||||
// channel reservation.
|
// channel reservation.
|
||||||
resCtx, err := alice.fundingMgr.getReservationCtx(bobPubKey, chanID)
|
resCtx, err := alice.fundingMgr.getReservationCtx(bobPubKey, chanID)
|
||||||
@@ -2844,6 +2980,13 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The optional channel fees that will be applied in the channel
|
||||||
|
// announcement phase. Both base fee and fee rate were provided
|
||||||
|
// in the channel open request.
|
||||||
|
if err := assertFees(&resCtx.forwardingPolicy, 42, 1337); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Also make sure the parameters are properly set on Bob's end.
|
// Also make sure the parameters are properly set on Bob's end.
|
||||||
resCtx, err = bob.fundingMgr.getReservationCtx(alicePubKey, chanID)
|
resCtx, err = bob.fundingMgr.getReservationCtx(alicePubKey, chanID)
|
||||||
require.NoError(t, err, "unable to find ctx")
|
require.NoError(t, err, "unable to find ctx")
|
||||||
@@ -2860,6 +3003,11 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
|||||||
maxValueInFlight, maxValueAcceptChannel); err != nil {
|
maxValueInFlight, maxValueAcceptChannel); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := assertFees(&resCtx.forwardingPolicy, 100, 1000); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Give the message to Bob.
|
// Give the message to Bob.
|
||||||
bob.fundingMgr.ProcessFundingMsg(fundingCreated, alice)
|
bob.fundingMgr.ProcessFundingMsg(fundingCreated, alice)
|
||||||
|
|
||||||
@@ -2886,6 +3034,18 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
|||||||
t.Fatal("OpenStatusUpdate was not OpenStatusUpdate_ChanPending")
|
t.Fatal("OpenStatusUpdate was not OpenStatusUpdate_ChanPending")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// After the funding is sigend and before the channel announcement
|
||||||
|
// we expect Alice and Bob to store their respective fees in the database.
|
||||||
|
forwardingPolicy, _ := alice.fundingMgr.getInitialFwdingPolicy(fundingSigned.ChanID)
|
||||||
|
if err := assertFees(forwardingPolicy, 42, 1337); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardingPolicy, _ = bob.fundingMgr.getInitialFwdingPolicy(fundingSigned.ChanID)
|
||||||
|
if err := assertFees(forwardingPolicy, 100, 1000); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for Alice to published the funding tx to the network.
|
// Wait for Alice to published the funding tx to the network.
|
||||||
var fundingTx *wire.MsgTx
|
var fundingTx *wire.MsgTx
|
||||||
select {
|
select {
|
||||||
@@ -2933,11 +3093,44 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
|||||||
// maxValueInFlight since Alice required him to use it.
|
// maxValueInFlight since Alice required him to use it.
|
||||||
maxHtlcArr := []lnwire.MilliSatoshi{maxValueAcceptChannel, maxValueInFlight}
|
maxHtlcArr := []lnwire.MilliSatoshi{maxValueAcceptChannel, maxValueInFlight}
|
||||||
|
|
||||||
assertChannelAnnouncements(t, alice, bob, capacity, minHtlcArr, maxHtlcArr)
|
// Alice should have custom fees set whereas Bob should see his
|
||||||
|
// configured default fees announced.
|
||||||
|
defaultBaseFee := bob.fundingMgr.cfg.DefaultRoutingPolicy.BaseFee
|
||||||
|
defaultFeerate := bob.fundingMgr.cfg.DefaultRoutingPolicy.FeeRate
|
||||||
|
baseFees := []lnwire.MilliSatoshi{lnwire.MilliSatoshi(baseFee), defaultBaseFee}
|
||||||
|
feeRates := []lnwire.MilliSatoshi{lnwire.MilliSatoshi(feeRate), defaultFeerate}
|
||||||
|
|
||||||
|
assertChannelAnnouncements(t, alice, bob, capacity, minHtlcArr, maxHtlcArr, baseFees, feeRates)
|
||||||
|
|
||||||
// The funding transaction is now confirmed, wait for the
|
// The funding transaction is now confirmed, wait for the
|
||||||
// OpenStatusUpdate_ChanOpen update
|
// OpenStatusUpdate_ChanOpen update
|
||||||
waitForOpenUpdate(t, updateChan)
|
waitForOpenUpdate(t, updateChan)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// After the announcement we expect Alice and Bob to have cleared
|
||||||
|
// the fees for the channel from the database.
|
||||||
|
_, err = alice.fundingMgr.getInitialFwdingPolicy(fundingSigned.ChanID)
|
||||||
|
if err != channeldb.ErrChannelNotFound {
|
||||||
|
err = fmt.Errorf("channel fees were expected to be deleted" +
|
||||||
|
" but were not")
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = bob.fundingMgr.getInitialFwdingPolicy(fundingSigned.ChanID)
|
||||||
|
if err != channeldb.ErrChannelNotFound {
|
||||||
|
err = fmt.Errorf("channel fees were expected to be deleted" +
|
||||||
|
" but were not")
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestFundingManagerMaxPendingChannels checks that trying to open another
|
// TestFundingManagerMaxPendingChannels checks that trying to open another
|
||||||
@@ -3818,7 +4011,7 @@ func TestFundingManagerZeroConf(t *testing.T) {
|
|||||||
|
|
||||||
// We'll now assert that both sides send ChannelAnnouncement and
|
// We'll now assert that both sides send ChannelAnnouncement and
|
||||||
// ChannelUpdate messages.
|
// ChannelUpdate messages.
|
||||||
assertChannelAnnouncements(t, alice, bob, fundingAmt, nil, nil)
|
assertChannelAnnouncements(t, alice, bob, fundingAmt, nil, nil, nil, nil)
|
||||||
|
|
||||||
// We'll now wait for the OpenStatusUpdate_ChanOpen update.
|
// We'll now wait for the OpenStatusUpdate_ChanOpen update.
|
||||||
waitForOpenUpdate(t, updateChan)
|
waitForOpenUpdate(t, updateChan)
|
||||||
@@ -3844,7 +4037,7 @@ func TestFundingManagerZeroConf(t *testing.T) {
|
|||||||
Tx: fundingTx,
|
Tx: fundingTx,
|
||||||
}
|
}
|
||||||
|
|
||||||
assertChannelAnnouncements(t, alice, bob, fundingAmt, nil, nil)
|
assertChannelAnnouncements(t, alice, bob, fundingAmt, nil, nil, nil, nil)
|
||||||
|
|
||||||
// Both Alice and Bob should send on reportScidChan.
|
// Both Alice and Bob should send on reportScidChan.
|
||||||
select {
|
select {
|
||||||
@@ -3873,4 +4066,8 @@ func TestFundingManagerZeroConf(t *testing.T) {
|
|||||||
// Assert that the channel state is deleted from the fundingmanager's
|
// Assert that the channel state is deleted from the fundingmanager's
|
||||||
// datastore.
|
// datastore.
|
||||||
assertNoChannelState(t, alice, bob, fundingOp)
|
assertNoChannelState(t, alice, bob, fundingOp)
|
||||||
|
|
||||||
|
// The forwarding policy for the channel announcement should
|
||||||
|
// have been deleted from the database, as the channel is announced.
|
||||||
|
assertNoFwdingPolicy(t, alice, bob, fundingOp)
|
||||||
}
|
}
|
||||||
+1928
-1877
File diff suppressed because it is too large.
Load diff
@@ -2190,6 +2190,33 @@ message OpenChannelRequest {
|
|||||||
attempted.
|
attempted.
|
||||||
*/
|
*/
|
||||||
bool scid_alias = 20;
|
bool scid_alias = 20;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The base fee charged regardless of the number of milli-satoshis sent.
|
||||||
|
*/
|
||||||
|
uint64 base_fee = 21;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The fee rate in ppm (parts per million) that will be charged in
|
||||||
|
proportion of the value of each forwarded HTLC.
|
||||||
|
*/
|
||||||
|
uint64 fee_rate = 22;
|
||||||
|
|
||||||
|
/*
|
||||||
|
If use_base_fee is true the open channel announcement will update the
|
||||||
|
channel base fee with the value specified in base_fee. In the case of
|
||||||
|
a base_fee of 0 use_base_fee is needed downstream to distinguish whether
|
||||||
|
to use the default base fee value specified in the config or 0.
|
||||||
|
*/
|
||||||
|
bool use_base_fee = 23;
|
||||||
|
|
||||||
|
/*
|
||||||
|
If use_fee_rate is true the open channel announcement will update the
|
||||||
|
channel fee rate with the value specified in fee_rate. In the case of
|
||||||
|
a fee_rate of 0 use_fee_rate is needed downstream to distinguish whether
|
||||||
|
to use the default fee rate value specified in the config or 0.
|
||||||
|
*/
|
||||||
|
bool use_fee_rate = 24;
|
||||||
}
|
}
|
||||||
message OpenStatusUpdate {
|
message OpenStatusUpdate {
|
||||||
oneof update {
|
oneof update {
|
||||||
|
|||||||
@@ -5661,6 +5661,24 @@
|
|||||||
"scid_alias": {
|
"scid_alias": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If this is true, then an option-scid-alias channel-type open will be\nattempted."
|
"description": "If this is true, then an option-scid-alias channel-type open will be\nattempted."
|
||||||
|
},
|
||||||
|
"base_fee": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64",
|
||||||
|
"description": "The base fee charged regardless of the number of milli-satoshis sent."
|
||||||
|
},
|
||||||
|
"fee_rate": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64",
|
||||||
|
"description": "The fee rate in ppm (parts per million) that will be charged in\nproportion of the value of each forwarded HTLC."
|
||||||
|
},
|
||||||
|
"use_base_fee": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If use_base_fee is true the open channel announcement will update the\nchannel base fee with the value specified in base_fee. In the case of\na base_fee of 0 use_base_fee is needed downstream to distinguish whether\nto use the default base fee value specified in the config or 0."
|
||||||
|
},
|
||||||
|
"use_fee_rate": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If use_fee_rate is true the open channel announcement will update the\nchannel fee rate with the value specified in fee_rate. In the case of\na fee_rate of 0 use_fee_rate is needed downstream to distinguish whether\nto use the default fee rate value specified in the config or 0."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+28
-2
@@ -92,7 +92,7 @@ type NetworkHarness struct {
|
|||||||
// NewNetworkHarness creates a new network test harness.
|
// NewNetworkHarness creates a new network test harness.
|
||||||
// TODO(roasbeef): add option to use golang's build library to a binary of the
|
// TODO(roasbeef): add option to use golang's build library to a binary of the
|
||||||
// current repo. This will save developers from having to manually `go install`
|
// current repo. This will save developers from having to manually `go install`
|
||||||
// within the repo each time before changes
|
// within the repo each time before changes.
|
||||||
func NewNetworkHarness(m *HarnessMiner, b BackendConfig, lndBinary string,
|
func NewNetworkHarness(m *HarnessMiner, b BackendConfig, lndBinary string,
|
||||||
dbBackend DatabaseBackend) (*NetworkHarness, error) {
|
dbBackend DatabaseBackend) (*NetworkHarness, error) {
|
||||||
|
|
||||||
@@ -240,6 +240,7 @@ out:
|
|||||||
|
|
||||||
if aliceResp.ConfirmedBalance == expectedBalance &&
|
if aliceResp.ConfirmedBalance == expectedBalance &&
|
||||||
bobResp.ConfirmedBalance == expectedBalance {
|
bobResp.ConfirmedBalance == expectedBalance {
|
||||||
|
|
||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
case <-balanceTimeout:
|
case <-balanceTimeout:
|
||||||
@@ -764,7 +765,7 @@ func (n *NetworkHarness) connectNodes(t *testing.T, a, b *HarnessNode,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DisconnectNodes disconnects node a from node b by sending RPC message
|
// DisconnectNodes disconnects node a from node b by sending RPC message
|
||||||
// from a node to b node
|
// from a node to b node.
|
||||||
func (n *NetworkHarness) DisconnectNodes(a, b *HarnessNode) error {
|
func (n *NetworkHarness) DisconnectNodes(a, b *HarnessNode) error {
|
||||||
ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout)
|
ctx, cancel := context.WithTimeout(n.runCtx, DefaultTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -998,6 +999,26 @@ type OpenChannelParams struct {
|
|||||||
// ScidAlias denotes whether the channel will be an option-scid-alias
|
// ScidAlias denotes whether the channel will be an option-scid-alias
|
||||||
// channel type negotiation.
|
// channel type negotiation.
|
||||||
ScidAlias bool
|
ScidAlias bool
|
||||||
|
|
||||||
|
// BaseFee is the channel base fee applied during the channel
|
||||||
|
// announcement phase.
|
||||||
|
BaseFee uint64
|
||||||
|
|
||||||
|
// FeeRate is the channel fee rate in ppm applied during the channel
|
||||||
|
// announcement phase.
|
||||||
|
FeeRate uint64
|
||||||
|
|
||||||
|
// UseBaseFee, if set, instructs the downstream logic to apply the
|
||||||
|
// user-specified channel base fee to the channel update announcement.
|
||||||
|
// If set to false it avoids applying a base fee of 0 and instead
|
||||||
|
// activates the default configured base fee.
|
||||||
|
UseBaseFee bool
|
||||||
|
|
||||||
|
// UseFeeRate, if set, instructs the downstream logic to apply the
|
||||||
|
// user-specified channel fee rate to the channel update announcement.
|
||||||
|
// If set to false it avoids applying a fee rate of 0 and instead
|
||||||
|
// activates the default configured fee rate.
|
||||||
|
UseFeeRate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenChannel attempts to open a channel between srcNode and destNode with the
|
// OpenChannel attempts to open a channel between srcNode and destNode with the
|
||||||
@@ -1038,6 +1059,10 @@ func (n *NetworkHarness) OpenChannel(srcNode, destNode *HarnessNode,
|
|||||||
CommitmentType: p.CommitmentType,
|
CommitmentType: p.CommitmentType,
|
||||||
ZeroConf: p.ZeroConf,
|
ZeroConf: p.ZeroConf,
|
||||||
ScidAlias: p.ScidAlias,
|
ScidAlias: p.ScidAlias,
|
||||||
|
BaseFee: p.BaseFee,
|
||||||
|
FeeRate: p.FeeRate,
|
||||||
|
UseBaseFee: p.UseBaseFee,
|
||||||
|
UseFeeRate: p.UseFeeRate,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to use n.runCtx here to keep the response stream alive after
|
// We need to use n.runCtx here to keep the response stream alive after
|
||||||
@@ -1223,6 +1248,7 @@ func (n *NetworkHarness) CloseChannel(lnNode *HarnessNode,
|
|||||||
// not.
|
// not.
|
||||||
filterChannel := func(node *HarnessNode,
|
filterChannel := func(node *HarnessNode,
|
||||||
op wire.OutPoint) (*lnrpc.Channel, error) {
|
op wire.OutPoint) (*lnrpc.Channel, error) {
|
||||||
|
|
||||||
listResp, err := node.ListChannels(ctxt, listReq)
|
listResp, err := node.ListChannels(ctxt, listReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -1330,7 +1330,7 @@ func (hn *HarnessNode) lightningNetworkWatcher() {
|
|||||||
|
|
||||||
// WaitForNetworkChannelOpen will block until a channel with the target
|
// WaitForNetworkChannelOpen will block until a channel with the target
|
||||||
// outpoint is seen as being fully advertised within the network. A channel is
|
// outpoint is seen as being fully advertised within the network. A channel is
|
||||||
// considered "fully advertised" once both of its directional edges has been
|
// considered "fully advertised" once both of its directional edges have been
|
||||||
// advertised within the test Lightning Network.
|
// advertised within the test Lightning Network.
|
||||||
func (hn *HarnessNode) WaitForNetworkChannelOpen(
|
func (hn *HarnessNode) WaitForNetworkChannelOpen(
|
||||||
chanPoint *lnrpc.ChannelPoint) error {
|
chanPoint *lnrpc.ChannelPoint) error {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func assertPolicyUpdate(t *harnessTest, nodes []*lntest.HarnessNode,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// testUpdateChannelPolicy tests that policy updates made to a channel
|
// testUpdateChannelPolicy tests that policy updates made to a channel
|
||||||
// gets propagated to other nodes in the network.
|
// get propagated to other nodes in the network.
|
||||||
func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) {
|
func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) {
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
|
|
||||||
@@ -466,7 +466,8 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
t, []*lntest.HarnessNode{net.Alice, net.Bob},
|
t, []*lntest.HarnessNode{net.Alice, net.Bob},
|
||||||
net.Alice.PubKeyStr, expectedPolicy, chanPoint3,
|
net.Alice.PubKeyStr, expectedPolicy, chanPoint3,
|
||||||
)
|
)
|
||||||
// Check that all nodes remembers the policy update
|
|
||||||
|
// Check that all nodes remember the policy update
|
||||||
// they received.
|
// they received.
|
||||||
assertChannelPolicy(
|
assertChannelPolicy(
|
||||||
t, net.Alice, net.Alice.PubKeyStr,
|
t, net.Alice, net.Alice.PubKeyStr,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/integration/rpctest"
|
"github.com/btcsuite/btcd/integration/rpctest"
|
||||||
|
"github.com/lightningnetwork/lnd/chainreg"
|
||||||
"github.com/lightningnetwork/lnd/funding"
|
"github.com/lightningnetwork/lnd/funding"
|
||||||
"github.com/lightningnetwork/lnd/lnrpc"
|
"github.com/lightningnetwork/lnd/lnrpc"
|
||||||
"github.com/lightningnetwork/lnd/lntest"
|
"github.com/lightningnetwork/lnd/lntest"
|
||||||
@@ -226,13 +227,15 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
|
||||||
chanGraph, err = net.Alice.DescribeGraph(ctxt, req)
|
chanGraph, err = net.Alice.DescribeGraph(ctxt, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
predErr = fmt.Errorf("unable to query for alice's routing table: %v", err)
|
predErr = fmt.Errorf("unable to query for "+
|
||||||
|
"alice's routing table: %v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
numEdges = len(chanGraph.Edges)
|
numEdges = len(chanGraph.Edges)
|
||||||
if numEdges != 0 {
|
if numEdges != 0 {
|
||||||
predErr = fmt.Errorf("expected to find no edge in the graph, found %d",
|
predErr = fmt.Errorf("expected to find "+
|
||||||
|
"no edge in the graph, found %d",
|
||||||
numEdges)
|
numEdges)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -249,10 +252,161 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) {
|
|||||||
closeReorgedChannelAndAssert(t, net, net.Alice, chanPoint, false)
|
closeReorgedChannelAndAssert(t, net, net.Alice, chanPoint, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testBasicChannelCreationAndUpdates tests multiple channel opening and closing,
|
// testOpenChannelFeePolicy checks if different channel fee scenarios
|
||||||
// and ensures that if a node is subscribed to channel updates they will be
|
// are correctly handled when the optional channel fee parameters
|
||||||
// received correctly for both cooperative and force closed channels.
|
// baseFee and feeRate are provided. If the OpenChannelRequest is not
|
||||||
func testBasicChannelCreationAndUpdates(net *lntest.NetworkHarness, t *harnessTest) {
|
// provided with a value for baseFee/feeRate the expectation is that the
|
||||||
|
// default baseFee/feeRate is applied.
|
||||||
|
// 1.) no params provided to OpenChannelRequest
|
||||||
|
// ChannelUpdate --> defaultBaseFee, defaultFeeRate
|
||||||
|
// 2.) only baseFee provided to OpenChannelRequest
|
||||||
|
// ChannelUpdate --> provided baseFee, defaultFeeRate
|
||||||
|
// 3.) only feeRate provided to OpenChannelRequest
|
||||||
|
// ChannelUpdate --> defaultBaseFee, provided FeeRate
|
||||||
|
// 4.) baseFee and feeRate provided to OpenChannelRequest
|
||||||
|
// ChannelUpdate --> provided baseFee, provided feeRate.
|
||||||
|
func testOpenChannelUpdateFeePolicy(net *lntest.NetworkHarness,
|
||||||
|
t *harnessTest) {
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultBaseFee = 1000
|
||||||
|
defaultFeeRate = 1
|
||||||
|
defaultTimeLockDelta = chainreg.DefaultBitcoinTimeLockDelta
|
||||||
|
defaultMinHtlc = 1000
|
||||||
|
optionalBaseFee = 1337
|
||||||
|
optionalFeeRate = 1337
|
||||||
|
)
|
||||||
|
|
||||||
|
defaultMaxHtlc := calculateMaxHtlc(funding.MaxBtcFundingAmount)
|
||||||
|
|
||||||
|
chanAmt := funding.MaxBtcFundingAmount
|
||||||
|
pushAmt := chanAmt / 2
|
||||||
|
|
||||||
|
feeScenarios := []lntest.OpenChannelParams{
|
||||||
|
{
|
||||||
|
Amt: chanAmt,
|
||||||
|
PushAmt: pushAmt,
|
||||||
|
UseBaseFee: false,
|
||||||
|
UseFeeRate: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Amt: chanAmt,
|
||||||
|
PushAmt: pushAmt,
|
||||||
|
BaseFee: optionalBaseFee,
|
||||||
|
UseBaseFee: true,
|
||||||
|
UseFeeRate: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Amt: chanAmt,
|
||||||
|
PushAmt: pushAmt,
|
||||||
|
FeeRate: optionalFeeRate,
|
||||||
|
UseBaseFee: false,
|
||||||
|
UseFeeRate: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Amt: chanAmt,
|
||||||
|
PushAmt: pushAmt,
|
||||||
|
BaseFee: optionalBaseFee,
|
||||||
|
FeeRate: optionalFeeRate,
|
||||||
|
UseBaseFee: true,
|
||||||
|
UseFeeRate: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedPolicies := []lnrpc.RoutingPolicy{
|
||||||
|
{
|
||||||
|
FeeBaseMsat: defaultBaseFee,
|
||||||
|
FeeRateMilliMsat: defaultFeeRate,
|
||||||
|
TimeLockDelta: defaultTimeLockDelta,
|
||||||
|
MinHtlc: defaultMinHtlc,
|
||||||
|
MaxHtlcMsat: defaultMaxHtlc,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
FeeBaseMsat: optionalBaseFee,
|
||||||
|
FeeRateMilliMsat: defaultFeeRate,
|
||||||
|
TimeLockDelta: defaultTimeLockDelta,
|
||||||
|
MinHtlc: defaultMinHtlc,
|
||||||
|
MaxHtlcMsat: defaultMaxHtlc,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
FeeBaseMsat: defaultBaseFee,
|
||||||
|
FeeRateMilliMsat: optionalFeeRate,
|
||||||
|
TimeLockDelta: defaultTimeLockDelta,
|
||||||
|
MinHtlc: defaultMinHtlc,
|
||||||
|
MaxHtlcMsat: defaultMaxHtlc,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
FeeBaseMsat: optionalBaseFee,
|
||||||
|
FeeRateMilliMsat: optionalFeeRate,
|
||||||
|
TimeLockDelta: defaultTimeLockDelta,
|
||||||
|
MinHtlc: defaultMinHtlc,
|
||||||
|
MaxHtlcMsat: defaultMaxHtlc,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
bobExpectedPolicy := lnrpc.RoutingPolicy{
|
||||||
|
FeeBaseMsat: defaultBaseFee,
|
||||||
|
FeeRateMilliMsat: defaultFeeRate,
|
||||||
|
TimeLockDelta: defaultTimeLockDelta,
|
||||||
|
MinHtlc: defaultMinHtlc,
|
||||||
|
MaxHtlcMsat: defaultMaxHtlc,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, feeScenario := range feeScenarios {
|
||||||
|
// Create a channel Alice->Bob.
|
||||||
|
chanPoint := openChannelAndAssert(
|
||||||
|
t, net, net.Alice, net.Bob,
|
||||||
|
feeScenario,
|
||||||
|
)
|
||||||
|
|
||||||
|
defer closeChannelAndAssert(t, net, net.Alice, chanPoint, false)
|
||||||
|
|
||||||
|
// We add all the nodes' update channels to a slice, such that we can
|
||||||
|
// make sure they all receive the expected updates.
|
||||||
|
nodes := []*lntest.HarnessNode{net.Alice, net.Bob}
|
||||||
|
|
||||||
|
// Alice and Bob should see each other's ChannelUpdates, advertising
|
||||||
|
// the preferred routing policies.
|
||||||
|
assertPolicyUpdate(
|
||||||
|
t, nodes, net.Alice.PubKeyStr,
|
||||||
|
&expectedPolicies[i], chanPoint,
|
||||||
|
)
|
||||||
|
assertPolicyUpdate(
|
||||||
|
t, nodes, net.Bob.PubKeyStr,
|
||||||
|
&bobExpectedPolicy, chanPoint,
|
||||||
|
)
|
||||||
|
|
||||||
|
// They should now know about the default policies.
|
||||||
|
for _, node := range nodes {
|
||||||
|
assertChannelPolicy(
|
||||||
|
t, node, net.Alice.PubKeyStr,
|
||||||
|
&expectedPolicies[i], chanPoint,
|
||||||
|
)
|
||||||
|
assertChannelPolicy(
|
||||||
|
t, node, net.Bob.PubKeyStr,
|
||||||
|
&bobExpectedPolicy, chanPoint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(
|
||||||
|
t.t, net.Alice.WaitForNetworkChannelOpen(chanPoint),
|
||||||
|
"alice reports channel opening",
|
||||||
|
)
|
||||||
|
|
||||||
|
require.NoError(
|
||||||
|
t.t, net.Bob.WaitForNetworkChannelOpen(chanPoint),
|
||||||
|
"bob reports channel opening",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// testBasicChannelCreationAndUpdates tests multiple channel opening and
|
||||||
|
// closing, and ensures that if a node is subscribed to channel updates
|
||||||
|
// they will be received correctly for both cooperative and force closed
|
||||||
|
// channels.
|
||||||
|
func testBasicChannelCreationAndUpdates(net *lntest.NetworkHarness,
|
||||||
|
t *harnessTest) {
|
||||||
|
|
||||||
runBasicChannelCreationAndUpdates(net, t, net.Alice, net.Bob)
|
runBasicChannelCreationAndUpdates(net, t, net.Alice, net.Bob)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -443,4 +443,8 @@ var allTestCases = []*testCase{
|
|||||||
name: "trackpayments",
|
name: "trackpayments",
|
||||||
test: testTrackPayments,
|
test: testTrackPayments,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "open channel fee policy",
|
||||||
|
test: testOpenChannelUpdateFeePolicy,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
+12
-1
@@ -1922,12 +1922,21 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
|
|||||||
// satoshis) does not exceed the amount the local party has requested
|
// satoshis) does not exceed the amount the local party has requested
|
||||||
// for funding.
|
// for funding.
|
||||||
//
|
//
|
||||||
// TODO(roasbeef): incorporate base fee?
|
|
||||||
if remoteInitialBalance >= localFundingAmt {
|
if remoteInitialBalance >= localFundingAmt {
|
||||||
return nil, fmt.Errorf("amount pushed to remote peer for " +
|
return nil, fmt.Errorf("amount pushed to remote peer for " +
|
||||||
"initial state must be below the local funding amount")
|
"initial state must be below the local funding amount")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine if the user provided channel fees
|
||||||
|
// and if so pass them on to the funding workflow.
|
||||||
|
var channelBaseFee, channelFeeRate *uint64
|
||||||
|
if in.UseBaseFee {
|
||||||
|
channelBaseFee = &in.BaseFee
|
||||||
|
}
|
||||||
|
if in.UseFeeRate {
|
||||||
|
channelFeeRate = &in.FeeRate
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that the user doesn't exceed the current soft-limit for
|
// Ensure that the user doesn't exceed the current soft-limit for
|
||||||
// channel size. If the funding amount is above the soft-limit, then
|
// channel size. If the funding amount is above the soft-limit, then
|
||||||
// we'll reject the request.
|
// we'll reject the request.
|
||||||
@@ -2086,6 +2095,8 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
|
|||||||
TargetPubkey: nodePubKey,
|
TargetPubkey: nodePubKey,
|
||||||
ChainHash: *r.cfg.ActiveNetParams.GenesisHash,
|
ChainHash: *r.cfg.ActiveNetParams.GenesisHash,
|
||||||
LocalFundingAmt: localFundingAmt,
|
LocalFundingAmt: localFundingAmt,
|
||||||
|
BaseFee: channelBaseFee,
|
||||||
|
FeeRate: channelFeeRate,
|
||||||
PushAmt: lnwire.NewMSatFromSatoshis(remoteInitialBalance),
|
PushAmt: lnwire.NewMSatFromSatoshis(remoteInitialBalance),
|
||||||
MinHtlcIn: minHtlcIn,
|
MinHtlcIn: minHtlcIn,
|
||||||
FundingFeePerKw: feeRate,
|
FundingFeePerKw: feeRate,
|
||||||
|
|||||||
Reference in new issue
Block a user