Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions ldk-server-grpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,124 @@ pub struct ListChannelPairForwardingStatsResponse {
#[prost(string, optional, tag = "2")]
pub next_page_token: ::core::option::Option<::prost::alloc::string::String>,
}
/// Lifetime earnings and on-chain costs for one open channel.
///
/// Revenue comes from LDK Node's per-channel forwarding counters. On-chain cost is
/// `fee_paid_msat` from recorded payments whose transaction type names this channel.
/// A transaction that names several channels has its fee split evenly across them, with
/// any leftover millisatoshis assigned to the first channel in that list.
/// `skimmed_fee_msat` is already included in `fee_earned_msat`.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "serde", serde(default))]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ChannelEarnings {
/// The channel id in hex-encoded form.
#[prost(string, tag = "1")]
pub channel_id: ::prost::alloc::string::String,
/// The hex-encoded public key of the channel counterparty.
#[prost(string, tag = "2")]
pub counterparty_node_id: ::prost::alloc::string::String,
/// The local `user_channel_id` as a decimal u128 string. `channel_id` can change once, when the funding
/// transaction is negotiated.
#[prost(string, tag = "3")]
pub user_channel_id: ::prost::alloc::string::String,
/// The value, in satoshis, of this channel as it appears in the funding output.
#[prost(uint64, tag = "4")]
pub channel_value_sats: u64,
/// Forwards where this was the incoming channel. Fees are attributed to this side.
/// A forward is also counted on the outgoing channel, so summing this with
/// `outbound_forwards` across channels counts each forward twice.
#[prost(uint64, tag = "5")]
pub inbound_forwards: u64,
/// Forwards where this was the outgoing channel.
#[prost(uint64, tag = "6")]
pub outbound_forwards: u64,
/// Unset when the fee is not known for every recorded forward.
#[prost(uint64, optional, tag = "7")]
pub fee_earned_msat: ::core::option::Option<u64>,
/// Total skimmed fees earned through this channel, in millisatoshis.
///
/// This is the share of `fee_earned_msat` that was withheld in addition to the
/// forwarding fee, not an amount earned on top of it. Adding the two would double-count.
#[prost(uint64, tag = "8")]
pub skimmed_fee_msat: u64,
/// One entry per transaction type that contributed a non-zero fee.
#[prost(message, repeated, tag = "9")]
pub onchain_costs: ::prost::alloc::vec::Vec<OnchainCost>,
/// Sum of `onchain_costs`.
#[prost(uint64, tag = "10")]
pub onchain_cost_msat: u64,
/// `fee_earned_msat` minus `onchain_cost_msat`. Unset when `fee_earned_msat` is unset.
#[prost(int64, optional, tag = "11")]
pub net_msat: ::core::option::Option<i64>,
}
/// Miner fee attributed to a channel for one on-chain transaction type.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "serde", serde(default))]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct OnchainCost {
/// The on-chain transaction type this fee came from.
#[prost(enumeration = "OnchainCostKind", tag = "1")]
pub kind: i32,
/// This channel's share of the miner fee for that transaction type, in millisatoshis.
#[prost(uint64, tag = "2")]
pub fee_paid_msat: u64,
}
/// Sum of the channel rows in the same response.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "serde", serde(default))]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ChannelEarningsTotals {
#[prost(uint64, tag = "1")]
pub channel_count: u64,
#[prost(uint64, tag = "2")]
pub inbound_forwards: u64,
#[prost(uint64, tag = "3")]
pub outbound_forwards: u64,
/// Unset when any channel's `fee_earned_msat` is unset.
#[prost(uint64, optional, tag = "4")]
pub fee_earned_msat: ::core::option::Option<u64>,
#[prost(uint64, tag = "5")]
pub skimmed_fee_msat: u64,
#[prost(message, repeated, tag = "6")]
pub onchain_costs: ::prost::alloc::vec::Vec<OnchainCost>,
#[prost(uint64, tag = "7")]
pub onchain_cost_msat: u64,
/// Unset when `fee_earned_msat` is unset.
#[prost(int64, optional, tag = "8")]
pub net_msat: ::core::option::Option<i64>,
#[prost(uint64, tag = "9")]
pub total_channel_value_sats: u64,
}
/// Lifetime profitability of each open channel.
///
/// Closed channels are omitted. A time range is not accepted: per-channel forwarding
/// stats are lifetime counters. Costs that name only closed channels, and sweeps with
/// no channel id, are omitted. `totals` is the sum of `channels`.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "serde", serde(default))]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ListChannelEarningsRequest {}
/// The response for the `ListChannelEarnings` RPC. On failure, a gRPC error status is returned.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[cfg_attr(feature = "serde", serde(default))]
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ListChannelEarningsResponse {
#[prost(message, repeated, tag = "1")]
pub channels: ::prost::alloc::vec::Vec<ChannelEarnings>,
#[prost(message, optional, tag = "2")]
pub totals: ::core::option::Option<ChannelEarningsTotals>,
}
/// Retrieves a paginated list of forwarded payments from LDK Node's detailed history.
/// LDK Node retains records for the current and previous one-hour buckets, then aggregates them
/// into hourly channel-pair statistics and removes the individual records.
Expand Down Expand Up @@ -1786,3 +1904,50 @@ pub struct GetPermissionsResponse {
#[prost(message, optional, tag = "1")]
pub macaroon: ::core::option::Option<MacaroonInfo>,
}
/// Matches `types.TransactionType`, without the channel ids that type also carries.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum OnchainCostKind {
Unspecified = 0,
Funding = 1,
CooperativeClose = 2,
UnilateralClose = 3,
AnchorBump = 4,
Claim = 5,
Sweep = 6,
InteractiveFunding = 7,
}
impl OnchainCostKind {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
OnchainCostKind::Unspecified => "ONCHAIN_COST_KIND_UNSPECIFIED",
OnchainCostKind::Funding => "ONCHAIN_COST_KIND_FUNDING",
OnchainCostKind::CooperativeClose => "ONCHAIN_COST_KIND_COOPERATIVE_CLOSE",
OnchainCostKind::UnilateralClose => "ONCHAIN_COST_KIND_UNILATERAL_CLOSE",
OnchainCostKind::AnchorBump => "ONCHAIN_COST_KIND_ANCHOR_BUMP",
OnchainCostKind::Claim => "ONCHAIN_COST_KIND_CLAIM",
OnchainCostKind::Sweep => "ONCHAIN_COST_KIND_SWEEP",
OnchainCostKind::InteractiveFunding => "ONCHAIN_COST_KIND_INTERACTIVE_FUNDING",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"ONCHAIN_COST_KIND_UNSPECIFIED" => Some(Self::Unspecified),
"ONCHAIN_COST_KIND_FUNDING" => Some(Self::Funding),
"ONCHAIN_COST_KIND_COOPERATIVE_CLOSE" => Some(Self::CooperativeClose),
"ONCHAIN_COST_KIND_UNILATERAL_CLOSE" => Some(Self::UnilateralClose),
"ONCHAIN_COST_KIND_ANCHOR_BUMP" => Some(Self::AnchorBump),
"ONCHAIN_COST_KIND_CLAIM" => Some(Self::Claim),
"ONCHAIN_COST_KIND_SWEEP" => Some(Self::Sweep),
"ONCHAIN_COST_KIND_INTERACTIVE_FUNDING" => Some(Self::InteractiveFunding),
_ => None,
}
}
}
1 change: 1 addition & 0 deletions ldk-server-grpc/src/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub const GET_FORWARDED_PAYMENT_TRACKING_MODE_PATH: &str = "GetForwardedPaymentT
pub const GET_CHANNEL_FORWARDING_STATS_PATH: &str = "GetChannelForwardingStats";
pub const LIST_CHANNEL_FORWARDING_STATS_PATH: &str = "ListChannelForwardingStats";
pub const LIST_CHANNEL_PAIR_FORWARDING_STATS_PATH: &str = "ListChannelPairForwardingStats";
pub const LIST_CHANNEL_EARNINGS_PATH: &str = "ListChannelEarnings";
pub const CREATE_MACAROON_PATH: &str = "CreateMacaroon";
pub const LIST_MACAROONS_PATH: &str = "ListMacaroons";
pub const REVOKE_MACAROON_PATH: &str = "RevokeMacaroon";
Expand Down
17 changes: 8 additions & 9 deletions ldk-server-grpc/src/permissions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ use crate::endpoints::{
GET_FORWARDED_PAYMENT_DETAILS_PATH, GET_FORWARDED_PAYMENT_TRACKING_MODE_PATH,
GET_NODE_INFO_PATH, GET_PAYMENT_DETAILS_PATH, GET_PERMISSIONS_PATH, GRAPH_GET_CHANNEL_PATH,
GRAPH_GET_NODE_PATH, GRAPH_LIST_CHANNELS_PATH, GRAPH_LIST_NODES_PATH, LIST_CHANNELS_PATH,
LIST_CHANNEL_FORWARDING_STATS_PATH, LIST_CHANNEL_PAIR_FORWARDING_STATS_PATH,
LIST_FORWARDED_PAYMENTS_PATH, LIST_MACAROONS_PATH, LIST_PAYMENTS_PATH, LIST_PEERS_PATH,
ONCHAIN_BUMP_FEE_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH,
REVOKE_MACAROON_PATH, SIGN_MESSAGE_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH,
SPONTANEOUS_SEND_PATH, SUBSCRIBE_EVENTS_PATH, UNIFIED_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH,
VERIFY_SIGNATURE_PATH,
LIST_CHANNEL_EARNINGS_PATH, LIST_CHANNEL_FORWARDING_STATS_PATH,
LIST_CHANNEL_PAIR_FORWARDING_STATS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_MACAROONS_PATH,
LIST_PAYMENTS_PATH, LIST_PEERS_PATH, ONCHAIN_BUMP_FEE_PATH, ONCHAIN_RECEIVE_PATH,
ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, REVOKE_MACAROON_PATH, SIGN_MESSAGE_PATH, SPLICE_IN_PATH,
SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, SUBSCRIBE_EVENTS_PATH, UNIFIED_SEND_PATH,
UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH,
};

pub const ADMIN_PERMISSION: &str = "admin";
Expand Down Expand Up @@ -188,9 +188,8 @@ pub fn method_authorization(method: &str) -> MethodAuthorization {
| GET_FORWARDED_PAYMENT_TRACKING_MODE_PATH
| GET_CHANNEL_FORWARDING_STATS_PATH
| LIST_CHANNEL_FORWARDING_STATS_PATH
| LIST_CHANNEL_PAIR_FORWARDING_STATS_PATH => {
MethodAuthorization::Permission(PAYMENTS_READ_PERMISSION)
},
| LIST_CHANNEL_PAIR_FORWARDING_STATS_PATH
| LIST_CHANNEL_EARNINGS_PATH => MethodAuthorization::Permission(PAYMENTS_READ_PERMISSION),
LIST_CHANNELS_PATH => MethodAuthorization::Permission(CHANNELS_READ_PERMISSION),
OPEN_CHANNEL_PATH
| UPDATE_CHANNEL_CONFIG_PATH
Expand Down
100 changes: 100 additions & 0 deletions ldk-server-grpc/src/proto/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,103 @@ message ListChannelPairForwardingStatsResponse {
optional string next_page_token = 2;
}

// Lifetime earnings and on-chain costs for one open channel.
//
// Revenue comes from LDK Node's per-channel forwarding counters. On-chain cost is
// `fee_paid_msat` from recorded payments whose transaction type names this channel.
// A transaction that names several channels has its fee split evenly across them, with
// any leftover millisatoshis assigned to the first channel in that list.
// `skimmed_fee_msat` is already included in `fee_earned_msat`.
message ChannelEarnings {
// The channel id in hex-encoded form.
string channel_id = 1;

// The hex-encoded public key of the channel counterparty.
string counterparty_node_id = 2;

// The local `user_channel_id` as a decimal u128 string. `channel_id` can change once, when the funding
// transaction is negotiated.
string user_channel_id = 3;

// The value, in satoshis, of this channel as it appears in the funding output.
uint64 channel_value_sats = 4;

// Forwards where this was the incoming channel. Fees are attributed to this side.
// A forward is also counted on the outgoing channel, so summing this with
// `outbound_forwards` across channels counts each forward twice.
uint64 inbound_forwards = 5;

// Forwards where this was the outgoing channel.
uint64 outbound_forwards = 6;

// Unset when the fee is not known for every recorded forward.
optional uint64 fee_earned_msat = 7;

// Total skimmed fees earned through this channel, in millisatoshis.
//
// This is the share of `fee_earned_msat` that was withheld in addition to the
// forwarding fee, not an amount earned on top of it. Adding the two would double-count.
uint64 skimmed_fee_msat = 8;

// One entry per transaction type that contributed a non-zero fee.
repeated OnchainCost onchain_costs = 9;

// Sum of `onchain_costs`.
uint64 onchain_cost_msat = 10;

// `fee_earned_msat` minus `onchain_cost_msat`. Unset when `fee_earned_msat` is unset.
optional int64 net_msat = 11;
}

// Miner fee attributed to a channel for one on-chain transaction type.
message OnchainCost {
// The on-chain transaction type this fee came from.
OnchainCostKind kind = 1;

// This channel's share of the miner fee for that transaction type, in millisatoshis.
uint64 fee_paid_msat = 2;
}

// Matches `types.TransactionType`, without the channel ids that type also carries.
enum OnchainCostKind {
ONCHAIN_COST_KIND_UNSPECIFIED = 0;
ONCHAIN_COST_KIND_FUNDING = 1;
ONCHAIN_COST_KIND_COOPERATIVE_CLOSE = 2;
ONCHAIN_COST_KIND_UNILATERAL_CLOSE = 3;
ONCHAIN_COST_KIND_ANCHOR_BUMP = 4;
ONCHAIN_COST_KIND_CLAIM = 5;
ONCHAIN_COST_KIND_SWEEP = 6;
ONCHAIN_COST_KIND_INTERACTIVE_FUNDING = 7;
}

// Sum of the channel rows in the same response.
message ChannelEarningsTotals {
uint64 channel_count = 1;
uint64 inbound_forwards = 2;
uint64 outbound_forwards = 3;
// Unset when any channel's `fee_earned_msat` is unset.
optional uint64 fee_earned_msat = 4;
uint64 skimmed_fee_msat = 5;
repeated OnchainCost onchain_costs = 6;
uint64 onchain_cost_msat = 7;
// Unset when `fee_earned_msat` is unset.
optional int64 net_msat = 8;
uint64 total_channel_value_sats = 9;
}

// Lifetime profitability of each open channel.
//
// Closed channels are omitted. A time range is not accepted: per-channel forwarding
// stats are lifetime counters. Costs that name only closed channels, and sweeps with
// no channel id, are omitted. `totals` is the sum of `channels`.
message ListChannelEarningsRequest {}

// The response for the `ListChannelEarnings` RPC. On failure, a gRPC error status is returned.
message ListChannelEarningsResponse {
repeated ChannelEarnings channels = 1;
ChannelEarningsTotals totals = 2;
}

// Retrieves a paginated list of forwarded payments from LDK Node's detailed history.
// LDK Node retains records for the current and previous one-hour buckets, then aggregates them
// into hourly channel-pair statistics and removes the individual records.
Expand Down Expand Up @@ -1324,6 +1421,9 @@ service LightningNode {
// List channel-pair forwarding statistics (paginated).
rpc ListChannelPairForwardingStats(ListChannelPairForwardingStatsRequest) returns (ListChannelPairForwardingStatsResponse);

// Lifetime profitability of each open channel.
rpc ListChannelEarnings(ListChannelEarningsRequest) returns (ListChannelEarningsResponse);

// List forwarded payments.
rpc ListForwardedPayments(ListForwardedPaymentsRequest) returns (ListForwardedPaymentsResponse);
// Connect to a peer.
Expand Down
1 change: 1 addition & 0 deletions ldk-server/src/macaroons/authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ mod tests {
("GetChannelForwardingStats", Some("payments:read")),
("ListChannelForwardingStats", Some("payments:read")),
("ListChannelPairForwardingStats", Some("payments:read")),
("ListChannelEarnings", Some("payments:read")),
("ConnectPeer", Some("peers:manage")),
("DisconnectPeer", Some("peers:manage")),
("ListPeers", Some("peers:read")),
Expand Down
Loading