mirror of
https://github.com/aljazceru/lightning.git
synced 2026-01-17 04:44:22 +01:00
830 lines
22 KiB
Rust
830 lines
22 KiB
Rust
use anyhow::Context;
|
|
use anyhow::{anyhow, Error, Result};
|
|
use bitcoin::hashes::Hash as BitcoinHash;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde::{Deserializer, Serializer};
|
|
use std::fmt::{Display, Formatter};
|
|
use std::str::FromStr;
|
|
use std::string::ToString;
|
|
|
|
pub use bitcoin::hashes::sha256::Hash as Sha256;
|
|
pub use bitcoin::secp256k1::PublicKey;
|
|
|
|
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
|
#[allow(non_camel_case_types)]
|
|
pub enum ChannelState {
|
|
OPENINGD = 0,
|
|
CHANNELD_AWAITING_LOCKIN = 1,
|
|
CHANNELD_NORMAL = 2,
|
|
CHANNELD_SHUTTING_DOWN = 3,
|
|
CLOSINGD_SIGEXCHANGE = 4,
|
|
CLOSINGD_COMPLETE = 5,
|
|
AWAITING_UNILATERAL = 6,
|
|
FUNDING_SPEND_SEEN = 7,
|
|
ONCHAIN = 8,
|
|
DUALOPEND_OPEN_INIT = 9,
|
|
DUALOPEND_AWAITING_LOCKIN = 10,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
|
#[allow(non_camel_case_types)]
|
|
pub enum HtlcState {
|
|
SENT_ADD_HTLC = 0,
|
|
SENT_ADD_COMMIT = 1,
|
|
RCVD_ADD_REVOCATION = 2,
|
|
RCVD_ADD_ACK_COMMIT = 3,
|
|
SENT_ADD_ACK_REVOCATION = 4,
|
|
RCVD_ADD_ACK_REVOCATION = 5,
|
|
RCVD_REMOVE_HTLC = 6,
|
|
RCVD_REMOVE_COMMIT = 7,
|
|
SENT_REMOVE_REVOCATION = 8,
|
|
SENT_REMOVE_ACK_COMMIT = 9,
|
|
RCVD_REMOVE_ACK_REVOCATION = 10,
|
|
RCVD_ADD_HTLC = 11,
|
|
RCVD_ADD_COMMIT = 12,
|
|
SENT_ADD_REVOCATION = 13,
|
|
SENT_ADD_ACK_COMMIT = 14,
|
|
SENT_REMOVE_HTLC = 15,
|
|
SENT_REMOVE_COMMIT = 16,
|
|
RCVD_REMOVE_REVOCATION = 17,
|
|
RCVD_REMOVE_ACK_COMMIT = 18,
|
|
SENT_REMOVE_ACK_REVOCATION = 19,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
|
#[allow(non_camel_case_types)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum ChannelStateChangeCause {
|
|
UNKNOWN,
|
|
LOCAL,
|
|
USER,
|
|
REMOTE,
|
|
PROTOCOL,
|
|
ONCHAIN,
|
|
}
|
|
|
|
/// An `Amount` that can also be `any`. Useful for cases in which you
|
|
/// want to delegate the Amount selection so someone else, e.g., an
|
|
/// amountless invoice.
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
pub enum AmountOrAny {
|
|
Amount(Amount),
|
|
Any,
|
|
}
|
|
|
|
/// An amount that can also be `all`. Useful for cases where you want
|
|
/// to delegate the amount computation to the cln node.
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
pub enum AmountOrAll {
|
|
Amount(Amount),
|
|
All,
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub struct Amount {
|
|
msat: u64,
|
|
}
|
|
|
|
impl Amount {
|
|
pub fn from_msat(msat: u64) -> Amount {
|
|
Amount { msat }
|
|
}
|
|
|
|
pub fn from_sat(sat: u64) -> Amount {
|
|
Amount { msat: 1_000 * sat }
|
|
}
|
|
|
|
pub fn from_btc(btc: u64) -> Amount {
|
|
Amount {
|
|
msat: 100_000_000_000 * btc,
|
|
}
|
|
}
|
|
|
|
pub fn msat(&self) -> u64 {
|
|
self.msat
|
|
}
|
|
}
|
|
|
|
impl std::ops::Add for Amount {
|
|
type Output = Amount;
|
|
|
|
fn add(self, rhs: Self) -> Self::Output {
|
|
Amount {
|
|
msat: self.msat + rhs.msat,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::ops::Sub for Amount {
|
|
type Output = Amount;
|
|
|
|
fn sub(self, rhs: Self) -> Self::Output {
|
|
Amount {
|
|
msat: self.msat - rhs.msat,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct ShortChannelId(u64);
|
|
|
|
impl Serialize for ShortChannelId {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.serialize_str(&self.to_string())
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for ShortChannelId {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
use serde::de::Error;
|
|
let s: String = Deserialize::deserialize(deserializer)?;
|
|
Ok(Self::from_str(&s).map_err(|e| Error::custom(e.to_string()))?)
|
|
}
|
|
}
|
|
|
|
impl FromStr for ShortChannelId {
|
|
type Err = crate::Error;
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
let parts: Result<Vec<u64>, _> = s.split('x').map(|p| p.parse()).collect();
|
|
let parts = parts.with_context(|| format!("Malformed short_channel_id: {}", s))?;
|
|
if parts.len() != 3 {
|
|
return Err(anyhow!(
|
|
"Malformed short_channel_id: element count mismatch"
|
|
));
|
|
}
|
|
|
|
Ok(ShortChannelId(
|
|
(parts[0] << 40) | (parts[1] << 16) | (parts[2] << 0),
|
|
))
|
|
}
|
|
}
|
|
impl ToString for ShortChannelId {
|
|
fn to_string(&self) -> String {
|
|
format!("{}x{}x{}", self.block(), self.txindex(), self.outnum())
|
|
}
|
|
}
|
|
impl ShortChannelId {
|
|
pub fn block(&self) -> u32 {
|
|
(self.0 >> 40) as u32 & 0xFFFFFF
|
|
}
|
|
pub fn txindex(&self) -> u32 {
|
|
(self.0 >> 16) as u32 & 0xFFFFFF
|
|
}
|
|
pub fn outnum(&self) -> u16 {
|
|
self.0 as u16 & 0xFFFF
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct Secret([u8; 32]);
|
|
|
|
impl TryFrom<Vec<u8>> for Secret {
|
|
type Error = crate::Error;
|
|
fn try_from(v: Vec<u8>) -> Result<Self, crate::Error> {
|
|
if v.len() != 32 {
|
|
Err(anyhow!("Unexpected secret length: {}", hex::encode(v)))
|
|
} else {
|
|
Ok(Secret(v.try_into().unwrap()))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Serialize for Secret {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.serialize_str(&hex::encode(&self.0))
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Secret {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
use serde::de::Error;
|
|
let s: String = Deserialize::deserialize(deserializer)?;
|
|
let h = hex::decode(s).map_err(|_| Error::custom("not a valid hex string"))?;
|
|
Ok(Secret(h.try_into().map_err(|_| {
|
|
Error::custom("not a valid hex-encoded hash")
|
|
})?))
|
|
}
|
|
}
|
|
|
|
impl Secret {
|
|
pub fn to_vec(self) -> Vec<u8> {
|
|
self.0.to_vec()
|
|
}
|
|
}
|
|
|
|
impl From<Secret> for [u8; 32] {
|
|
fn from(s: Secret) -> [u8; 32] {
|
|
s.0
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct Outpoint {
|
|
pub txid: Sha256,
|
|
pub outnum: u32,
|
|
}
|
|
|
|
impl Serialize for Outpoint {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.serialize_str(&format!("{}:{}", hex::encode(&self.txid), self.outnum))
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Outpoint {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
use serde::de::Error;
|
|
let s: String = Deserialize::deserialize(deserializer)?;
|
|
|
|
let splits: Vec<&str> = s.split(':').collect();
|
|
if splits.len() != 2 {
|
|
return Err(Error::custom("not a valid txid:output tuple"));
|
|
}
|
|
|
|
let txid_bytes =
|
|
hex::decode(splits[0]).map_err(|_| Error::custom("not a valid hex encoded txid"))?;
|
|
|
|
let txid = Sha256::from_slice(&txid_bytes)
|
|
.map_err(|e| Error::custom(format!("Invalid TxId: {}", e)))?;
|
|
|
|
let outnum: u32 = splits[1]
|
|
.parse()
|
|
.map_err(|e| Error::custom(format!("{} is not a valid number: {}", s, e)))?;
|
|
|
|
Ok(Outpoint { txid, outnum })
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum ChannelSide {
|
|
LOCAL,
|
|
REMOTE,
|
|
}
|
|
|
|
impl TryFrom<i32> for ChannelSide {
|
|
type Error = crate::Error;
|
|
|
|
fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
|
|
match value {
|
|
0 => Ok(ChannelSide::LOCAL),
|
|
1 => Ok(ChannelSide::REMOTE),
|
|
_ => Err(anyhow!(
|
|
"Invalid ChannelSide mapping, only 0 or 1 are allowed"
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<i32> for ChannelState {
|
|
type Error = crate::Error;
|
|
|
|
fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
|
|
match value {
|
|
0 => Ok(ChannelState::OPENINGD),
|
|
1 => Ok(ChannelState::CHANNELD_AWAITING_LOCKIN),
|
|
2 => Ok(ChannelState::CHANNELD_NORMAL),
|
|
3 => Ok(ChannelState::CHANNELD_SHUTTING_DOWN),
|
|
4 => Ok(ChannelState::CLOSINGD_SIGEXCHANGE),
|
|
5 => Ok(ChannelState::CLOSINGD_COMPLETE),
|
|
6 => Ok(ChannelState::AWAITING_UNILATERAL),
|
|
7 => Ok(ChannelState::FUNDING_SPEND_SEEN),
|
|
8 => Ok(ChannelState::ONCHAIN),
|
|
9 => Ok(ChannelState::DUALOPEND_OPEN_INIT),
|
|
10 => Ok(ChannelState::DUALOPEND_AWAITING_LOCKIN),
|
|
_ => Err(anyhow!("Invalid channel state {}", value)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<i32> for HtlcState {
|
|
fn from(value: i32) -> Self {
|
|
match value {
|
|
0 => HtlcState::SENT_ADD_HTLC,
|
|
1 => HtlcState::SENT_ADD_COMMIT,
|
|
2 => HtlcState::RCVD_ADD_REVOCATION,
|
|
3 => HtlcState::RCVD_ADD_ACK_COMMIT,
|
|
4 => HtlcState::SENT_ADD_ACK_REVOCATION,
|
|
5 => HtlcState::RCVD_ADD_ACK_REVOCATION,
|
|
6 => HtlcState::RCVD_REMOVE_HTLC,
|
|
7 => HtlcState::RCVD_REMOVE_COMMIT,
|
|
8 => HtlcState::SENT_REMOVE_REVOCATION,
|
|
9 => HtlcState::SENT_REMOVE_ACK_COMMIT,
|
|
10 => HtlcState::RCVD_REMOVE_ACK_REVOCATION,
|
|
11 => HtlcState::RCVD_ADD_HTLC,
|
|
12 => HtlcState::RCVD_ADD_COMMIT,
|
|
13 => HtlcState::SENT_ADD_REVOCATION,
|
|
14 => HtlcState::SENT_ADD_ACK_COMMIT,
|
|
15 => HtlcState::SENT_REMOVE_HTLC,
|
|
16 => HtlcState::SENT_REMOVE_COMMIT,
|
|
17 => HtlcState::RCVD_REMOVE_REVOCATION,
|
|
18 => HtlcState::RCVD_REMOVE_ACK_COMMIT,
|
|
19 => HtlcState::SENT_REMOVE_ACK_REVOCATION,
|
|
|
|
n => panic!("Unmapped HtlcState variant: {}", n),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Amount {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
use serde::de::Error;
|
|
|
|
let any: serde_json::Value = Deserialize::deserialize(deserializer)?;
|
|
|
|
// Amount fields used to be a string with the unit "msat" or
|
|
// "sat" as a suffix. The great consolidation in PR #5306
|
|
// changed that to always be a `u64`, but for backwards
|
|
// compatibility we need to handle both cases.
|
|
let ires: Option<u64> = any.as_u64();
|
|
// TODO(cdecker): Remove string parsing support once the great msat purge is complete
|
|
let sres: Option<&str> = any.as_str();
|
|
|
|
match (ires, sres) {
|
|
(Some(i), _) => {
|
|
// Notice that this assumes the field is denominated in `msat`
|
|
Ok(Amount::from_msat(i))
|
|
}
|
|
(_, Some(s)) => s
|
|
.try_into()
|
|
.map_err(|_e| Error::custom("could not parse amount")),
|
|
(None, _) => {
|
|
// We reuse the integer parsing error as that's the
|
|
// default after the great msat purge of 2022.
|
|
Err(Error::custom("could not parse amount"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Serialize for Amount {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.serialize_str(&format!("{}msat", self.msat))
|
|
}
|
|
}
|
|
|
|
impl Serialize for AmountOrAll {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
match self {
|
|
AmountOrAll::Amount(a) => serializer.serialize_str(&format!("{}msat", a.msat)),
|
|
AmountOrAll::All => serializer.serialize_str("all"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Serialize for AmountOrAny {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
match self {
|
|
AmountOrAny::Amount(a) => serializer.serialize_str(&format!("{}msat", a.msat)),
|
|
AmountOrAny::Any => serializer.serialize_str("any"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for AmountOrAny {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s: String = Deserialize::deserialize(deserializer)?;
|
|
Ok(match s.to_lowercase().as_ref() {
|
|
"any" => AmountOrAny::Any,
|
|
v => AmountOrAny::Amount(
|
|
v.try_into()
|
|
.map_err(|_e| serde::de::Error::custom("could not parse amount"))?,
|
|
),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for AmountOrAll {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s: String = Deserialize::deserialize(deserializer)?;
|
|
Ok(match s.to_lowercase().as_ref() {
|
|
"all" => AmountOrAll::All,
|
|
v => AmountOrAll::Amount(
|
|
v.try_into()
|
|
.map_err(|_e| serde::de::Error::custom("could not parse amount"))?,
|
|
),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&str> for Amount {
|
|
type Error = Error;
|
|
fn try_from(s: &str) -> Result<Amount> {
|
|
let number: u64 = s
|
|
.chars()
|
|
.map(|c| c.to_digit(10))
|
|
.take_while(|opt| opt.is_some())
|
|
.fold(0, |acc, digit| acc * 10 + (digit.unwrap() as u64));
|
|
|
|
let s = s.to_lowercase();
|
|
if s.ends_with("msat") {
|
|
Ok(Amount::from_msat(number))
|
|
} else if s.ends_with("sat") {
|
|
Ok(Amount::from_sat(number))
|
|
} else if s.ends_with("btc") {
|
|
Ok(Amount::from_btc(number))
|
|
} else {
|
|
Err(anyhow!("Unable to parse amount from string: {}", s))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Amount> for String {
|
|
fn from(a: Amount) -> String {
|
|
format!("{}msat", a.msat)
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub enum Feerate {
|
|
Slow,
|
|
Normal,
|
|
Urgent,
|
|
PerKb(u32),
|
|
PerKw(u32),
|
|
}
|
|
|
|
impl TryFrom<&str> for Feerate {
|
|
type Error = Error;
|
|
fn try_from(s: &str) -> Result<Feerate> {
|
|
let number: u32 = s
|
|
.chars()
|
|
.map(|c| c.to_digit(10))
|
|
.take_while(|opt| opt.is_some())
|
|
.fold(0, |acc, digit| acc * 10 + (digit.unwrap() as u32));
|
|
|
|
let s = s.to_lowercase();
|
|
if s.ends_with("perkw") {
|
|
Ok(Feerate::PerKw(number))
|
|
} else if s.ends_with("perkb") {
|
|
Ok(Feerate::PerKb(number))
|
|
} else if s == "slow" {
|
|
Ok(Feerate::Slow)
|
|
} else if s == "normal" {
|
|
Ok(Feerate::Normal)
|
|
} else if s == "urgent" {
|
|
Ok(Feerate::Urgent)
|
|
} else {
|
|
Err(anyhow!("Unable to parse feerate from string: {}", s))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&Feerate> for String {
|
|
fn from(f: &Feerate) -> String {
|
|
match f {
|
|
Feerate::Slow => "slow".to_string(),
|
|
Feerate::Normal => "normal".to_string(),
|
|
Feerate::Urgent => "urgent".to_string(),
|
|
Feerate::PerKb(v) => format!("{}perkb", v),
|
|
Feerate::PerKw(v) => format!("{}perkw", v),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Feerate {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let s: String = Deserialize::deserialize(deserializer)?;
|
|
let res: Feerate = s
|
|
.as_str()
|
|
.try_into()
|
|
.map_err(|e| serde::de::Error::custom(format!("{}", e)))?;
|
|
Ok(res)
|
|
}
|
|
}
|
|
|
|
impl Serialize for Feerate {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
let s: String = self.into();
|
|
serializer.serialize_str(&s)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_amount_serde() {
|
|
#[derive(Serialize, PartialEq, Debug, Deserialize)]
|
|
struct T {
|
|
amount: Amount,
|
|
}
|
|
|
|
let tests = vec![
|
|
("{\"amount\": \"10msat\"}", Amount { msat: 10 }, "10msat"),
|
|
(
|
|
"{\"amount\": \"42sat\"}",
|
|
Amount { msat: 42_000 },
|
|
"42000msat",
|
|
),
|
|
(
|
|
"{\"amount\": \"31337btc\"}",
|
|
Amount {
|
|
msat: 3_133_700_000_000_000,
|
|
},
|
|
"3133700000000000msat",
|
|
),
|
|
];
|
|
|
|
for (req, res, s) in tests.into_iter() {
|
|
println!("{:?} {:?}", req, res);
|
|
let parsed: T = serde_json::from_str(req).unwrap();
|
|
assert_eq!(res, parsed.amount);
|
|
|
|
let serialized: String = parsed.amount.into();
|
|
assert_eq!(s, serialized);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_amount_all_any() {
|
|
let t = r#"{"any": "any", "all": "all", "not_any": "42msat", "not_all": "31337msat"}"#;
|
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
|
struct T {
|
|
all: AmountOrAll,
|
|
not_all: AmountOrAll,
|
|
any: AmountOrAny,
|
|
not_any: AmountOrAny,
|
|
}
|
|
|
|
let parsed: T = serde_json::from_str(t).unwrap();
|
|
|
|
let expected = T {
|
|
all: AmountOrAll::All,
|
|
any: AmountOrAny::Any,
|
|
not_all: AmountOrAll::Amount(Amount { msat: 31337 }),
|
|
not_any: AmountOrAny::Amount(Amount { msat: 42 }),
|
|
};
|
|
assert_eq!(expected, parsed);
|
|
|
|
let serialized: String = serde_json::to_string(&parsed).unwrap();
|
|
assert_eq!(
|
|
serialized,
|
|
r#"{"all":"all","not_all":"31337msat","any":"any","not_any":"42msat"}"#
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_feerate() {
|
|
let tests = vec![
|
|
("slow", Feerate::Slow),
|
|
("normal", Feerate::Normal),
|
|
("urgent", Feerate::Urgent),
|
|
("12345perkb", Feerate::PerKb(12345)),
|
|
("54321perkw", Feerate::PerKw(54321)),
|
|
];
|
|
|
|
for (input, output) in tests.into_iter() {
|
|
let parsed: Feerate = input.try_into().unwrap();
|
|
assert_eq!(parsed, output);
|
|
let serialized: String = (&parsed).into();
|
|
assert_eq!(serialized, input);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_output_desc() {
|
|
let a = r#"{"address":"1234msat"}"#;
|
|
let od = serde_json::from_str(a).unwrap();
|
|
|
|
assert_eq!(
|
|
OutputDesc {
|
|
address: "address".to_string(),
|
|
amount: Amount { msat: 1234 }
|
|
},
|
|
od
|
|
);
|
|
let serialized: String = serde_json::to_string(&od).unwrap();
|
|
assert_eq!(a, serialized);
|
|
}
|
|
|
|
#[test]
|
|
fn tlvstream() {
|
|
let stream = TlvStream {
|
|
entries: vec![
|
|
TlvEntry {
|
|
typ: 31337,
|
|
value: vec![1, 2, 3, 4, 5],
|
|
},
|
|
TlvEntry {
|
|
typ: 42,
|
|
value: vec![],
|
|
},
|
|
],
|
|
};
|
|
|
|
let res = serde_json::to_string(&stream).unwrap();
|
|
assert_eq!(res, "{\"31337\":\"0102030405\",\"42\":\"\"}");
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct OutputDesc {
|
|
pub address: String,
|
|
pub amount: Amount,
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for OutputDesc {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let map: std::collections::HashMap<String, Amount> =
|
|
Deserialize::deserialize(deserializer)?;
|
|
|
|
let (address, amount) = map.iter().next().unwrap();
|
|
|
|
Ok(OutputDesc {
|
|
address: address.to_string(),
|
|
amount: *amount,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Serialize for OutputDesc {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
use serde::ser::SerializeMap;
|
|
let mut map = serializer.serialize_map(Some(1))?;
|
|
map.serialize_key(&self.address)?;
|
|
map.serialize_value(&self.amount)?;
|
|
map.end()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct Routehop {
|
|
pub id: PublicKey,
|
|
pub scid: ShortChannelId,
|
|
pub feebase: Amount,
|
|
pub feeprop: u32,
|
|
pub expirydelta: u16,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Routehint {
|
|
pub hops: Vec<Routehop>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct RoutehintList {
|
|
pub hints: Vec<Routehint>,
|
|
}
|
|
|
|
use serde::ser::SerializeSeq;
|
|
|
|
impl Serialize for Routehint {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
let mut seq = serializer.serialize_seq(Some(self.hops.len()))?;
|
|
for e in self.hops.iter() {
|
|
seq.serialize_element(e)?;
|
|
}
|
|
seq.end()
|
|
}
|
|
}
|
|
|
|
impl Serialize for RoutehintList {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
let mut seq = serializer.serialize_seq(Some(self.hints.len()))?;
|
|
for e in self.hints.iter() {
|
|
seq.serialize_element(e)?;
|
|
}
|
|
seq.end()
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for RoutehintList {
|
|
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
todo!("Required once we roundtrip, but not necessary for cln-rpc itself")
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for Routehint {
|
|
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
todo!("Required once we roundtrip, but not necessary for cln-rpc itself")
|
|
}
|
|
}
|
|
|
|
/// An error returned by the lightningd RPC consisting of a code and a
|
|
/// message
|
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
|
pub struct RpcError {
|
|
pub code: Option<i32>,
|
|
pub message: String,
|
|
}
|
|
|
|
impl Display for RpcError {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
if let Some(code) = self.code {
|
|
write!(f, "Error code {}: {}", code, self.message)
|
|
} else {
|
|
write!(f, "Error: {}", self.message)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for RpcError {}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct TlvEntry {
|
|
pub typ: u64,
|
|
pub value: Vec<u8>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct TlvStream {
|
|
pub entries: Vec<TlvEntry>,
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for TlvStream {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let map: std::collections::HashMap<u64, String> = Deserialize::deserialize(deserializer)?;
|
|
|
|
let entries = map
|
|
.iter()
|
|
.map(|(k, v)| TlvEntry {
|
|
typ: *k,
|
|
value: hex::decode(v).unwrap(),
|
|
})
|
|
.collect();
|
|
|
|
Ok(TlvStream { entries })
|
|
}
|
|
}
|
|
|
|
impl Serialize for TlvStream {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
use serde::ser::SerializeMap;
|
|
|
|
let mut map = serializer.serialize_map(Some(self.entries.len()))?;
|
|
for e in &self.entries {
|
|
map.serialize_key(&e.typ)?;
|
|
map.serialize_value(&hex::encode(&e.value))?;
|
|
}
|
|
map.end()
|
|
}
|
|
}
|