Merge pull request #4371 from yyforyongyu/expose-channel-constraints

lnrpc: expose all local and remote channel constraints
This commit is contained in:
Conner Fromknecht 2020-07-01 16:45:35 -07:00 committed by GitHub
commit 01ba4d0b59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1271 additions and 866 deletions

View File

@ -861,7 +861,7 @@ func closeAllChannels(ctx *cli.Context) error {
"The closing transaction will need %d "+
"confirmations before the funds can be "+
"spent. (yes/no): ", channel.RemotePubkey,
channel.ChannelPoint, channel.CsvDelay)
channel.ChannelPoint, channel.LocalConstraints.CsvDelay)
confirmed := promptForConfirmation(msg)

File diff suppressed because it is too large Load Diff

View File

@ -993,6 +993,30 @@ enum CommitmentType {
UNKNOWN_COMMITMENT_TYPE = 999;
}
message ChannelConstraints {
/*
The CSV delay expressed in relative blocks. If the channel is force closed,
we will need to wait for this many blocks before we can regain our funds.
*/
uint32 csv_delay = 1;
// The minimum satoshis this node is required to reserve in its balance.
uint64 chan_reserve_sat = 2;
// The dust limit (in satoshis) of the initiator's commitment tx.
uint64 dust_limit_sat = 3;
// The maximum amount of coins in millisatoshis that can be pending in this
// channel.
uint64 max_pending_amt_msat = 4;
// The smallest HTLC in millisatoshis that the initiator will accept.
uint64 min_htlc_msat = 5;
// The total number of incoming HTLC's that the initiator will accept.
uint32 max_accepted_htlcs = 6;
}
message Channel {
// Whether this channel is active or not
bool active = 1;
@ -1065,10 +1089,11 @@ message Channel {
repeated HTLC pending_htlcs = 15;
/*
The CSV delay expressed in relative blocks. If the channel is force closed,
we will need to wait for this many blocks before we can regain our funds.
Deprecated. The CSV delay expressed in relative blocks. If the channel is
force closed, we will need to wait for this many blocks before we can regain
our funds.
*/
uint32 csv_delay = 16;
uint32 csv_delay = 16 [deprecated = true];
// Whether this channel is advertised to the network or not.
bool private = 17;
@ -1079,13 +1104,15 @@ message Channel {
// A set of flags showing the current state of the channel.
string chan_status_flags = 19;
// The minimum satoshis this node is required to reserve in its balance.
int64 local_chan_reserve_sat = 20;
// Deprecated. The minimum satoshis this node is required to reserve in its
// balance.
int64 local_chan_reserve_sat = 20 [deprecated = true];
/*
The minimum satoshis the other node is required to reserve in its balance.
Deprecated. The minimum satoshis the other node is required to reserve in
its balance.
*/
int64 remote_chan_reserve_sat = 21;
int64 remote_chan_reserve_sat = 21 [deprecated = true];
// Deprecated. Use commitment_type.
bool static_remote_key = 22 [deprecated = true];
@ -1133,6 +1160,12 @@ message Channel {
value of zero will mean the channel has no additional restrictions.
*/
uint32 thaw_height = 28;
// List constraints for the local node.
ChannelConstraints local_constraints = 29;
// List constraints for the remote node.
ChannelConstraints remote_constraints = 30;
}
message ListChannelsRequest {

View File

@ -2551,7 +2551,7 @@
"csv_delay": {
"type": "integer",
"format": "int64",
"description": "The CSV delay expressed in relative blocks. If the channel is force closed,\nwe will need to wait for this many blocks before we can regain our funds."
"description": "Deprecated. The CSV delay expressed in relative blocks. If the channel is\nforce closed, we will need to wait for this many blocks before we can regain\nour funds."
},
"private": {
"type": "boolean",
@ -2570,12 +2570,12 @@
"local_chan_reserve_sat": {
"type": "string",
"format": "int64",
"description": "The minimum satoshis this node is required to reserve in its balance."
"description": "Deprecated. The minimum satoshis this node is required to reserve in its\nbalance."
},
"remote_chan_reserve_sat": {
"type": "string",
"format": "int64",
"description": "The minimum satoshis the other node is required to reserve in its balance."
"description": "Deprecated. The minimum satoshis the other node is required to reserve in\nits balance."
},
"static_remote_key": {
"type": "boolean",
@ -2609,6 +2609,14 @@
"type": "integer",
"format": "int64",
"description": "This uint32 indicates if this channel is to be considered 'frozen'. A\nfrozen channel doest not allow a cooperative channel close by the\ninitiator. The thaw_height is the height that this restriction stops\napplying to the channel. This field is optional, not setting it or using a\nvalue of zero will mean the channel has no additional restrictions."
},
"local_constraints": {
"$ref": "#/definitions/lnrpcChannelConstraints",
"description": "List constraints for the local node."
},
"remote_constraints": {
"$ref": "#/definitions/lnrpcChannelConstraints",
"description": "List constraints for the remote node."
}
}
},
@ -2794,6 +2802,41 @@
}
}
},
"lnrpcChannelConstraints": {
"type": "object",
"properties": {
"csv_delay": {
"type": "integer",
"format": "int64",
"description": "The CSV delay expressed in relative blocks. If the channel is force closed,\nwe will need to wait for this many blocks before we can regain our funds."
},
"chan_reserve_sat": {
"type": "string",
"format": "uint64",
"description": "The minimum satoshis this node is required to reserve in its balance."
},
"dust_limit_sat": {
"type": "string",
"format": "uint64",
"description": "The dust limit (in satoshis) of the initiator's commitment tx."
},
"max_pending_amt_msat": {
"type": "string",
"format": "uint64",
"description": "The maximum amount of coins in millisatoshis that can be pending in this\nchannel."
},
"min_htlc_msat": {
"type": "string",
"format": "uint64",
"description": "The smallest HTLC in millisatoshis that the initiator will accept."
},
"max_accepted_htlcs": {
"type": "integer",
"format": "int64",
"description": "The total number of incoming HTLC's that the initiator will accept."
}
}
},
"lnrpcChannelEdge": {
"type": "object",
"properties": {

View File

@ -46,6 +46,7 @@ import (
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
@ -2677,7 +2678,7 @@ func testDisconnectingTargetPeer(net *lntest.NetworkHarness, t *harnessTest) {
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, alice)
if err != nil {
t.Fatalf("unable to send coins to carol: %v", err)
t.Fatalf("unable to send coins to alice: %v", err)
}
chanAmt := lnd.MaxBtcFundingAmount
@ -4455,6 +4456,187 @@ func testSphinxReplayPersistence(net *lntest.NetworkHarness, t *harnessTest) {
cleanupForceClose(t, net, carol, chanPoint)
}
func assertChannelConstraintsEqual(
t *harnessTest, want, got *lnrpc.ChannelConstraints) {
if want.CsvDelay != got.CsvDelay {
t.Fatalf("CsvDelay mismatched, want: %v, got: %v",
want.CsvDelay, got.CsvDelay,
)
}
if want.ChanReserveSat != got.ChanReserveSat {
t.Fatalf("ChanReserveSat mismatched, want: %v, got: %v",
want.ChanReserveSat, got.ChanReserveSat,
)
}
if want.DustLimitSat != got.DustLimitSat {
t.Fatalf("DustLimitSat mismatched, want: %v, got: %v",
want.DustLimitSat, got.DustLimitSat,
)
}
if want.MaxPendingAmtMsat != got.MaxPendingAmtMsat {
t.Fatalf("MaxPendingAmtMsat mismatched, want: %v, got: %v",
want.MaxPendingAmtMsat, got.MaxPendingAmtMsat,
)
}
if want.MinHtlcMsat != got.MinHtlcMsat {
t.Fatalf("MinHtlcMsat mismatched, want: %v, got: %v",
want.MinHtlcMsat, got.MinHtlcMsat,
)
}
if want.MaxAcceptedHtlcs != got.MaxAcceptedHtlcs {
t.Fatalf("MaxAcceptedHtlcs mismatched, want: %v, got: %v",
want.MaxAcceptedHtlcs, got.MaxAcceptedHtlcs,
)
}
}
// testListChannels checks that the response from ListChannels is correct. It
// tests the values in all ChannelConstraints are returned as expected. Once
// ListChannels becomes mature, a test against all fields in ListChannels should
// be performed.
func testListChannels(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
// Create two fresh nodes and open a channel between them.
alice, err := net.NewNode("Alice", nil)
if err != nil {
t.Fatalf("unable to create new node: %v", err)
}
defer shutdownAndAssert(net, t, alice)
bob, err := net.NewNode("Bob", nil)
if err != nil {
t.Fatalf("unable to create new node: %v", err)
}
defer shutdownAndAssert(net, t, bob)
// Connect Alice to Bob.
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
if err := net.ConnectNodes(ctxb, alice, bob); err != nil {
t.Fatalf("unable to connect alice to bob: %v", err)
}
// Give Alice some coins so she can fund a channel.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, alice)
if err != nil {
t.Fatalf("unable to send coins to alice: %v", err)
}
// Open a channel with 100k satoshis between Alice and Bob with Alice
// being the sole funder of the channel. The minial HTLC amount is set to
// 4200 msats.
const customizedMinHtlc = 4200
chanAmt := btcutil.Amount(100000)
ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout)
chanPoint := openChannelAndAssert(
ctxt, t, net, alice, bob,
lntest.OpenChannelParams{
Amt: chanAmt,
MinHtlc: customizedMinHtlc,
},
)
// Wait for Alice and Bob to receive the channel edge from the
// funding manager.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = alice.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("alice didn't see the alice->bob channel before "+
"timeout: %v", err)
}
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = bob.WaitForNetworkChannelOpen(ctxt, chanPoint)
if err != nil {
t.Fatalf("bob didn't see the bob->alice channel before "+
"timeout: %v", err)
}
// Alice should have one channel opened with Bob.
assertNodeNumChannels(t, alice, 1)
// Bob should have one channel opened with Alice.
assertNodeNumChannels(t, bob, 1)
// Get the ListChannel response from Alice.
listReq := &lnrpc.ListChannelsRequest{}
ctxb = context.Background()
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err := alice.ListChannels(ctxt, listReq)
if err != nil {
t.Fatalf("unable to query for %s's channel list: %v",
alice.Name(), err)
}
// Check the returned response is correct.
aliceChannel := resp.Channels[0]
// defaultConstraints is a ChannelConstraints with default values. It is
// used to test against Alice's local channel constraints.
defaultConstraints := &lnrpc.ChannelConstraints{
CsvDelay: 4,
ChanReserveSat: 1000,
DustLimitSat: uint64(lnwallet.DefaultDustLimit()),
MaxPendingAmtMsat: 99000000,
MinHtlcMsat: 1,
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
}
assertChannelConstraintsEqual(
t, aliceChannel.LocalConstraints, defaultConstraints,
)
// customizedConstraints is a ChannelConstraints with customized values.
// Ideally, all these values can be passed in when creating the channel.
// Currently, only the MinHtlcMsat is customized. It is used to check
// against Alice's remote channel constratins.
customizedConstraints := &lnrpc.ChannelConstraints{
CsvDelay: 4,
ChanReserveSat: 1000,
DustLimitSat: uint64(lnwallet.DefaultDustLimit()),
MaxPendingAmtMsat: 99000000,
MinHtlcMsat: customizedMinHtlc,
MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
}
assertChannelConstraintsEqual(
t, aliceChannel.RemoteConstraints, customizedConstraints,
)
// Get the ListChannel response for Bob.
listReq = &lnrpc.ListChannelsRequest{}
ctxb = context.Background()
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
resp, err = bob.ListChannels(ctxt, listReq)
if err != nil {
t.Fatalf("unable to query for %s's channel "+
"list: %v", bob.Name(), err)
}
bobChannel := resp.Channels[0]
if bobChannel.ChannelPoint != aliceChannel.ChannelPoint {
t.Fatalf("Bob's channel point mismatched, want: %s, got: %s",
chanPoint.String(), bobChannel.ChannelPoint,
)
}
// Check channel constraints match. Alice's local channel constraint should
// be equal to Bob's remote channel constraint, and her remote one should
// be equal to Bob's local one.
assertChannelConstraintsEqual(
t, aliceChannel.LocalConstraints, bobChannel.RemoteConstraints,
)
assertChannelConstraintsEqual(
t, aliceChannel.RemoteConstraints, bobChannel.LocalConstraints,
)
}
func testListPayments(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
@ -13912,7 +14094,7 @@ func testHoldInvoicePersistence(net *lntest.NetworkHarness, t *harnessTest) {
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
err = carol.WaitForNetworkChannelOpen(ctxt, chanPointAlice)
if err != nil {
t.Fatalf("alice didn't see the alice->carol channel before "+
t.Fatalf("carol didn't see the carol->alice channel before "+
"timeout: %v", err)
}
@ -14585,6 +14767,10 @@ var testsCases = []*testCase{
name: "sphinx replay persistence",
test: testSphinxReplayPersistence,
},
{
name: "list channels",
test: testListChannels,
},
{
name: "list outgoing payments",
test: testListPayments,

View File

@ -3295,6 +3295,21 @@ func rpcCommitmentType(chanType channeldb.ChannelType) lnrpc.CommitmentType {
return lnrpc.CommitmentType_LEGACY
}
// createChannelConstraint creates a *lnrpc.ChannelConstraints using the
// *Channeldb.ChannelConfig.
func createChannelConstraint(
chanCfg *channeldb.ChannelConfig) *lnrpc.ChannelConstraints {
return &lnrpc.ChannelConstraints{
CsvDelay: uint32(chanCfg.CsvDelay),
ChanReserveSat: uint64(chanCfg.ChanReserve),
DustLimitSat: uint64(chanCfg.DustLimit),
MaxPendingAmtMsat: uint64(chanCfg.MaxPendingAmount),
MinHtlcMsat: uint64(chanCfg.MinHTLC),
MaxAcceptedHtlcs: uint32(chanCfg.MaxAcceptedHtlcs),
}
}
// createRPCOpenChannel creates an *lnrpc.Channel from the *channeldb.Channel.
func createRPCOpenChannel(r *rpcServer, graph *channeldb.ChannelGraph,
dbChannel *channeldb.OpenChannel, isActive bool) (*lnrpc.Channel, error) {
@ -3351,14 +3366,21 @@ func createRPCOpenChannel(r *rpcServer, graph *channeldb.ChannelGraph,
TotalSatoshisReceived: int64(dbChannel.TotalMSatReceived.ToSatoshis()),
NumUpdates: localCommit.CommitHeight,
PendingHtlcs: make([]*lnrpc.HTLC, len(localCommit.Htlcs)),
CsvDelay: uint32(dbChannel.LocalChanCfg.CsvDelay),
Initiator: dbChannel.IsInitiator,
ChanStatusFlags: dbChannel.ChanStatus().String(),
LocalChanReserveSat: int64(dbChannel.LocalChanCfg.ChanReserve),
RemoteChanReserveSat: int64(dbChannel.RemoteChanCfg.ChanReserve),
StaticRemoteKey: commitmentType == lnrpc.CommitmentType_STATIC_REMOTE_KEY,
CommitmentType: commitmentType,
ThawHeight: dbChannel.ThawHeight,
LocalConstraints: createChannelConstraint(
&dbChannel.LocalChanCfg,
),
RemoteConstraints: createChannelConstraint(
&dbChannel.RemoteChanCfg,
),
// TODO: remove the following deprecated fields
CsvDelay: uint32(dbChannel.LocalChanCfg.CsvDelay),
LocalChanReserveSat: int64(dbChannel.LocalChanCfg.ChanReserve),
RemoteChanReserveSat: int64(dbChannel.RemoteChanCfg.ChanReserve),
}
for i, htlc := range localCommit.Htlcs {