use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::fmt::{self, Debug, Display, Formatter};
use std::hash::Hash;
use std::io;
use std::str::FromStr;
use amplify::flags::FlagVec;
use lightning_encoding::{self, LightningDecode, LightningEncode};
#[cfg(feature = "serde")]
use serde_with::{As, DisplayFromStr};
#[derive(
Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Debug, Display, Error,
From
)]
#[display(doc_comments)]
pub enum Error {
#[from]
FeaturesInconsistency(NoRequiredFeatureError),
UnknownEvenFeature(u16),
}
#[derive(
Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, Debug, Display, Error
)]
#[display(doc_comments)]
pub enum NoRequiredFeatureError {
GossipQueries,
VarOptionOptin,
PaymentSecret,
OptionStaticRemotekey,
}
pub trait FeatureContext:
Display
+ Debug
+ Copy
+ Clone
+ PartialEq
+ Eq
+ PartialOrd
+ Ord
+ Hash
+ Default
{
}
#[derive(
Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display, Default
)]
#[display("I", alt = "init")]
pub struct InitContext;
impl FeatureContext for InitContext {}
#[derive(
Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display, Default
)]
#[display("N", alt = "node_announcement")]
pub struct NodeAnnouncementContext;
impl FeatureContext for NodeAnnouncementContext {}
#[derive(
Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display, Default
)]
#[display("C", alt = "channel_announcement")]
pub struct ChannelAnnouncementContext;
impl FeatureContext for ChannelAnnouncementContext {}
#[derive(
Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display, Default
)]
#[display("9", alt = "bolt11")]
pub struct Bolt11Context;
impl FeatureContext for Bolt11Context {}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)]
#[non_exhaustive]
#[repr(u16)]
pub enum Feature {
#[display("option_data_loss_protect", alt = "0/1")]
OptionDataLossProtect = 0,
#[display("initial_routing_sync", alt = "3")]
InitialRoutingSync = 2,
#[display("option_data_loss_protect", alt = "4/5")]
OptionUpfrontShutdownScript = 4,
#[display("gossip_queries", alt = "6/7")]
GossipQueries = 6,
#[display("var_onion_optin", alt = "8/9")]
VarOnionOptin = 8,
#[display("gossip_queries_ex", alt = "10/11")]
GossipQueriesEx = 10,
#[display("option_static_remotekey", alt = "12/13")]
OptionStaticRemotekey = 12,
#[display("payment_secret", alt = "14/15")]
PaymentSecret = 14,
#[display("basic_mpp", alt = "16/17")]
BasicMpp = 16,
#[display("option_support_large_channel", alt = "18/19")]
OptionSupportLargeChannel = 18,
#[display("option_anchor_outputs", alt = "20/21")]
OptionAnchorOutputs = 20,
#[display("option_anchors_zero_fee_htlc_tx", alt = "22/23")]
OptionAnchorZeroFeeHtlcTx = 22,
#[display("option_shutdown_anysegwit", alt = "26/27")]
OptionShutdownAnySegwit = 26,
#[display("option_channel_type", alt = "44/45")]
OptionChannelType = 44,
#[display("option_scid_alias", alt = "46/47")]
OptionScidAlias = 46,
#[display("option_payment_metadata", alt = "48/49")]
OptionPaymentMetadata = 48,
#[display("option_zeroconf", alt = "50/51")]
OptionZeroConf = 50,
}
impl Feature {
pub fn all() -> &'static [Feature] {
&[
Feature::OptionDataLossProtect,
Feature::InitialRoutingSync,
Feature::OptionUpfrontShutdownScript,
Feature::GossipQueries,
Feature::VarOnionOptin,
Feature::GossipQueriesEx,
Feature::OptionStaticRemotekey,
Feature::PaymentSecret,
Feature::BasicMpp,
Feature::OptionSupportLargeChannel,
Feature::OptionAnchorOutputs,
Feature::OptionAnchorZeroFeeHtlcTx,
Feature::OptionShutdownAnySegwit,
Feature::OptionChannelType,
Feature::OptionScidAlias,
Feature::OptionPaymentMetadata,
Feature::OptionZeroConf,
]
}
pub fn bit(self, required: bool) -> Option<u16> {
if self == Feature::InitialRoutingSync && required {
return None;
}
Some(self as u16 + !required as u16)
}
}
#[derive(
Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error, From
)]
#[display("the provided feature name is not known: {0}")]
pub struct UnknownFeatureError(pub String);
impl FromStr for Feature {
type Err = UnknownFeatureError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let feature = match s {
s if s == Feature::OptionDataLossProtect.to_string() => {
Feature::OptionDataLossProtect
}
s if s == Feature::InitialRoutingSync.to_string() => {
Feature::InitialRoutingSync
}
s if s == Feature::OptionUpfrontShutdownScript.to_string() => {
Feature::OptionUpfrontShutdownScript
}
s if s == Feature::GossipQueries.to_string() => {
Feature::GossipQueries
}
s if s == Feature::VarOnionOptin.to_string() => {
Feature::VarOnionOptin
}
s if s == Feature::GossipQueriesEx.to_string() => {
Feature::GossipQueriesEx
}
s if s == Feature::OptionStaticRemotekey.to_string() => {
Feature::OptionStaticRemotekey
}
s if s == Feature::PaymentSecret.to_string() => {
Feature::PaymentSecret
}
s if s == Feature::BasicMpp.to_string() => Feature::BasicMpp,
s if s == Feature::OptionSupportLargeChannel.to_string() => {
Feature::OptionSupportLargeChannel
}
s if s == Feature::OptionAnchorOutputs.to_string() => {
Feature::OptionAnchorOutputs
}
s if s == Feature::OptionAnchorZeroFeeHtlcTx.to_string() => {
Feature::OptionAnchorZeroFeeHtlcTx
}
s if s == Feature::OptionShutdownAnySegwit.to_string() => {
Feature::OptionShutdownAnySegwit
}
s if s == Feature::OptionChannelType.to_string() => {
Feature::OptionChannelType
}
s if s == Feature::OptionScidAlias.to_string() => {
Feature::OptionScidAlias
}
s if s == Feature::OptionPaymentMetadata.to_string() => {
Feature::OptionPaymentMetadata
}
s if s == Feature::OptionZeroConf.to_string() => {
Feature::OptionZeroConf
}
other => return Err(UnknownFeatureError(other.to_owned())),
};
Ok(feature)
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate")
)]
pub struct InitFeatures {
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub option_data_loss_protect: Option<bool>,
pub initial_routing_sync: bool,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub option_upfront_shutdown_script: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub gossip_queries: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub var_onion_optin: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub gossip_queries_ex: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub option_static_remotekey: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub payment_secret: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub basic_mpp: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub option_support_large_channel: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub option_anchor_outputs: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub option_anchors_zero_fee_htlc_tx: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub option_shutdown_anysegwit: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub option_channel_type: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub option_scid_alias: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub option_payment_metadata: Option<bool>,
#[cfg_attr(
feature = "serde",
serde(with = "As::<Option<DisplayFromStr>>")
)]
pub option_zeroconf: Option<bool>,
#[cfg_attr(feature = "serde", serde(with = "As::<DisplayFromStr>"))]
pub unknown: FlagVec,
}
impl Display for InitFeatures {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
for (feature, required) in self.known_set_features() {
Display::fmt(&feature, f)?;
if !required {
f.write_str("?")?;
}
f.write_str(", ")?;
}
Ok(())
}
}
impl InitFeatures {
pub fn byte_len(&self) -> u16 {
let max_known = Feature::all()
.iter()
.map(|f| f.bit(false).or_else(|| f.bit(true)).unwrap_or(0))
.max()
.unwrap_or(0);
let max_unknown = Iterator::max(self.unknown.iter()).unwrap_or(0);
max_known.max(max_unknown)
}
pub fn check(&self) -> Result<(), Error> {
self.check_consistency()?;
self.check_unknown_even()
}
pub fn check_consistency(&self) -> Result<(), NoRequiredFeatureError> {
if self.gossip_queries_ex.is_some() && self.gossip_queries.is_none() {
return Err(NoRequiredFeatureError::GossipQueries);
}
if self.payment_secret.is_some() && self.var_onion_optin.is_none() {
return Err(NoRequiredFeatureError::VarOptionOptin);
}
if self.basic_mpp.is_some() && self.payment_secret.is_none() {
return Err(NoRequiredFeatureError::PaymentSecret);
}
if self.option_anchor_outputs.is_some()
&& self.option_static_remotekey.is_none()
{
return Err(NoRequiredFeatureError::OptionStaticRemotekey);
}
Ok(())
}
pub fn check_unknown_even(&self) -> Result<(), Error> {
if let Some(flag) = self.unknown.iter().find(|flag| flag % 2 == 0) {
return Err(Error::UnknownEvenFeature(flag));
}
Ok(())
}
pub fn known_set_features(&self) -> BTreeMap<Feature, bool> {
let mut map = bmap! {};
if let Some(required) = self.option_data_loss_protect {
map.insert(Feature::OptionDataLossProtect, required);
}
if self.initial_routing_sync {
map.insert(Feature::InitialRoutingSync, false);
}
if let Some(required) = self.option_upfront_shutdown_script {
map.insert(Feature::OptionUpfrontShutdownScript, required);
}
if let Some(required) = self.gossip_queries {
map.insert(Feature::GossipQueries, required);
}
if let Some(required) = self.var_onion_optin {
map.insert(Feature::VarOnionOptin, required);
}
if let Some(required) = self.gossip_queries_ex {
map.insert(Feature::GossipQueriesEx, required);
}
if let Some(required) = self.option_static_remotekey {
map.insert(Feature::OptionStaticRemotekey, required);
}
if let Some(required) = self.payment_secret {
map.insert(Feature::PaymentSecret, required);
}
if let Some(required) = self.basic_mpp {
map.insert(Feature::BasicMpp, required);
}
if let Some(required) = self.option_support_large_channel {
map.insert(Feature::OptionSupportLargeChannel, required);
}
if let Some(required) = self.option_anchor_outputs {
map.insert(Feature::OptionAnchorOutputs, required);
}
if let Some(required) = self.option_anchors_zero_fee_htlc_tx {
map.insert(Feature::OptionAnchorZeroFeeHtlcTx, required);
}
if let Some(required) = self.option_shutdown_anysegwit {
map.insert(Feature::OptionShutdownAnySegwit, required);
}
if let Some(required) = self.option_channel_type {
map.insert(Feature::OptionChannelType, required);
}
if let Some(required) = self.option_scid_alias {
map.insert(Feature::OptionScidAlias, required);
}
if let Some(required) = self.option_payment_metadata {
map.insert(Feature::OptionPaymentMetadata, required);
}
if let Some(required) = self.option_zeroconf {
map.insert(Feature::OptionZeroConf, required);
}
map
}
}
impl TryFrom<FlagVec> for InitFeatures {
type Error = Error;
fn try_from(flags: FlagVec) -> Result<Self, Self::Error> {
let requirements = |feature: Feature| -> Option<bool> {
if let Some(true) = feature.bit(false).map(|bit| flags.is_set(bit))
{
Some(false)
} else if let Some(true) =
feature.bit(true).map(|bit| flags.is_set(bit))
{
Some(true)
} else {
None
}
};
let mut parsed = InitFeatures {
option_data_loss_protect: requirements(
Feature::OptionDataLossProtect,
),
initial_routing_sync: flags.is_set(3),
option_upfront_shutdown_script: requirements(
Feature::OptionUpfrontShutdownScript,
),
gossip_queries: requirements(Feature::GossipQueries),
var_onion_optin: requirements(Feature::VarOnionOptin),
gossip_queries_ex: requirements(Feature::GossipQueriesEx),
option_static_remotekey: requirements(
Feature::OptionStaticRemotekey,
),
payment_secret: requirements(Feature::PaymentSecret),
basic_mpp: requirements(Feature::BasicMpp),
option_support_large_channel: requirements(
Feature::OptionSupportLargeChannel,
),
option_anchor_outputs: requirements(Feature::OptionAnchorOutputs),
option_anchors_zero_fee_htlc_tx: requirements(
Feature::OptionAnchorZeroFeeHtlcTx,
),
option_shutdown_anysegwit: requirements(
Feature::OptionShutdownAnySegwit,
),
option_channel_type: requirements(Feature::OptionChannelType),
option_scid_alias: requirements(Feature::OptionScidAlias),
option_payment_metadata: requirements(
Feature::OptionPaymentMetadata,
),
option_zeroconf: requirements(Feature::OptionZeroConf),
unknown: none!(),
};
parsed.unknown = flags ^ parsed.clone().into();
parsed.unknown.shrink();
parsed.check()?;
Ok(parsed)
}
}
impl From<InitFeatures> for FlagVec {
fn from(features: InitFeatures) -> Self {
let flags = features.unknown.shrunk();
features.known_set_features().into_iter().fold(
flags,
|mut flags, (feature, required)| {
flags.set(feature.bit(required).expect(
"InitFeatures feature flag specification is broken",
));
flags
},
)
}
}
impl LightningEncode for InitFeatures {
fn lightning_encode<E: io::Write>(
&self,
e: E,
) -> Result<usize, lightning_encoding::Error> {
FlagVec::from(self.clone()).lightning_encode(e)
}
}
impl LightningDecode for InitFeatures {
fn lightning_decode<D: io::Read>(
d: D,
) -> Result<Self, lightning_encoding::Error> {
let flag_vec = FlagVec::lightning_decode(d)?;
InitFeatures::try_from(flag_vec).map_err(|e| {
lightning_encoding::Error::DataIntegrityError(e.to_string())
})
}
}
#[cfg(feature = "strict_encoding")]
impl strict_encoding::StrictEncode for InitFeatures {
fn strict_encode<E: io::Write>(
&self,
e: E,
) -> Result<usize, strict_encoding::Error> {
FlagVec::from(self.clone()).strict_encode(e)
}
}
#[cfg(feature = "strict_encoding")]
impl strict_encoding::StrictDecode for InitFeatures {
fn strict_decode<D: io::Read>(
d: D,
) -> Result<Self, strict_encoding::Error> {
let vec = FlagVec::strict_decode(d)?;
InitFeatures::try_from(vec).map_err(|e| {
strict_encoding::Error::DataIntegrityError(e.to_string())
})
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate")
)]
pub struct ChannelFeatures {}
impl TryFrom<FlagVec> for ChannelFeatures {
type Error = Error;
fn try_from(_: FlagVec) -> Result<Self, Self::Error> {
Ok(ChannelFeatures {})
}
}
impl From<ChannelFeatures> for FlagVec {
fn from(_: ChannelFeatures) -> Self {
FlagVec::default()
}
}
impl LightningEncode for ChannelFeatures {
fn lightning_encode<E: io::Write>(
&self,
e: E,
) -> Result<usize, lightning_encoding::Error> {
FlagVec::from(*self).lightning_encode(e)
}
}
impl LightningDecode for ChannelFeatures {
fn lightning_decode<D: io::Read>(
d: D,
) -> Result<Self, lightning_encoding::Error> {
let flag_vec = FlagVec::lightning_decode(d)?;
ChannelFeatures::try_from(flag_vec).map_err(|e| {
lightning_encoding::Error::DataIntegrityError(e.to_string())
})
}
}
#[cfg(feature = "strict_encoding")]
impl strict_encoding::StrictEncode for ChannelFeatures {
fn strict_encode<E: io::Write>(
&self,
e: E,
) -> Result<usize, strict_encoding::Error> {
FlagVec::from(*self).strict_encode(e)
}
}
#[cfg(feature = "strict_encoding")]
impl strict_encoding::StrictDecode for ChannelFeatures {
fn strict_decode<D: io::Read>(
d: D,
) -> Result<Self, strict_encoding::Error> {
let vec = FlagVec::strict_decode(d)?;
ChannelFeatures::try_from(vec).map_err(|e| {
strict_encoding::Error::DataIntegrityError(e.to_string())
})
}
}