Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Pending on #191] add invoice status and check logic in TLC #239

Open
wants to merge 5 commits into
base: yukang-more-on-payment-session
Choose a base branch
from
Open
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
73 changes: 63 additions & 10 deletions src/fiber/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
network::{get_chain_hash, SendOnionPacketCommand},
types::{ChannelUpdate, TlcErr, TlcErrPacket, TlcErrorCode},
},
invoice::InvoiceStore,
invoice::{CkbInvoice, CkbInvoiceStatus, InvoiceStore},
};
use ckb_hash::{blake2b_256, new_blake2b};
use ckb_sdk::Since;
Expand Down Expand Up @@ -683,6 +683,11 @@ where
let error_code = match error {
ProcessingChannelError::PeelingOnionPacketError(_) => TlcErrorCode::InvalidOnionPayload,
ProcessingChannelError::TlcForwardFeeIsTooLow => TlcErrorCode::FeeInsufficient,
ProcessingChannelError::FinalInvoiceInvalid(status) => match status {
CkbInvoiceStatus::Expired => TlcErrorCode::InvoiceExpired,
CkbInvoiceStatus::Cancelled => TlcErrorCode::InvoiceCancelled,
_ => TlcErrorCode::IncorrectOrUnknownPaymentDetails,
},
ProcessingChannelError::FinalIncorrectPreimage
| ProcessingChannelError::FinalIncorrectPaymentHash => {
TlcErrorCode::IncorrectOrUnknownPaymentDetails
Expand Down Expand Up @@ -728,18 +733,33 @@ where

fn try_to_settle_down_tlc(&self, state: &mut ChannelActorState) {
let tlcs = state.get_tlcs_for_settle_down();
let mut update_invoice_payment_hash = false;
for tlc_info in tlcs {
let tlc = tlc_info.tlc.clone();
if let Some(invoice) = self.store.get_invoice(&tlc.payment_hash) {
if invoice.is_expired() {
let command = RemoveTlcCommand {
id: tlc.get_id(),
reason: RemoveTlcReason::RemoveTlcFail(TlcErrPacket::new(TlcErr::new(
TlcErrorCode::InvoiceExpired,
))),
};
let result = self.handle_remove_tlc_command(state, command);
info!("try to settle down tlc: {:?} result: {:?}", &tlc, &result);
let status = self.get_invoice_status(&invoice);
match status {
CkbInvoiceStatus::Expired | CkbInvoiceStatus::Cancelled => {
let error_code = match status {
CkbInvoiceStatus::Expired => TlcErrorCode::InvoiceExpired,
CkbInvoiceStatus::Cancelled => TlcErrorCode::InvoiceCancelled,
_ => unreachable!(),
};
let command = RemoveTlcCommand {
id: tlc.get_id(),
reason: RemoveTlcReason::RemoveTlcFail(TlcErrPacket::new(TlcErr::new(
error_code,
))),
};
let result = self.handle_remove_tlc_command(state, command);
info!("try to settle down tlc: {:?} result: {:?}", &tlc, &result);
}
CkbInvoiceStatus::Paid => {
unreachable!("Paid invoice shold not be paid again");
}
_ => {
update_invoice_payment_hash = true;
}
}
}

Expand All @@ -762,6 +782,11 @@ where
};
let result = self.handle_remove_tlc_command(state, command);
info!("try to settle down tlc: {:?} result: {:?}", &tlc, &result);
if result.is_ok() && update_invoice_payment_hash {
let _ = self
.store
.update_invoice_status(&tlc.payment_hash, CkbInvoiceStatus::Paid);
}
// we only handle one tlc at a time.
break;
}
Expand All @@ -780,6 +805,7 @@ where
// try to fulfill the payment, find the corresponding payment preimage from payment hash.
let mut preimage = None;
let mut peeled_packet_bytes: Option<Vec<u8>> = None;
let mut update_invoice_payment_hash: Option<Hash256> = None;

if !add_tlc.onion_packet.is_empty() {
// TODO: Here we call network actor to peel the onion packet. Indeed, this message is forwarded from
Expand Down Expand Up @@ -815,6 +841,15 @@ where
return Err(ProcessingChannelError::FinalIncorrectHTLCAmount);
}

let payment_hash = add_tlc.payment_hash;
if let Some(invoice) = self.store.get_invoice(&payment_hash) {
let invoice_status = self.get_invoice_status(&invoice);
if invoice_status != CkbInvoiceStatus::Open {
return Err(ProcessingChannelError::FinalInvoiceInvalid(invoice_status));
}
update_invoice_payment_hash = Some(payment_hash);
}

// if this is the last hop, store the preimage.
// though we will RemoveTlcFulfill the TLC in try_to_settle_down_tlc function,
// here we can do error check early here for better error handling.
Expand Down Expand Up @@ -853,6 +888,11 @@ where

let tlc = state.create_inbounding_tlc(add_tlc.clone(), preimage)?;
state.insert_tlc(tlc.clone())?;
if let Some(payment_hash) = update_invoice_payment_hash {
self.store
.update_invoice_status(&payment_hash, CkbInvoiceStatus::Received)
.expect("update invoice status failed");
}
if let Some(ref udt_type_script) = state.funding_udt_type_script {
self.subscribers
.pending_received_tlcs_subscribers
Expand Down Expand Up @@ -1502,6 +1542,17 @@ where
}
}
}

fn get_invoice_status(&self, invoice: &CkbInvoice) -> CkbInvoiceStatus {
match self
.store
.get_invoice_status(&invoice.payment_hash())
.expect("no invoice status found")
{
CkbInvoiceStatus::Open if invoice.is_expired() => CkbInvoiceStatus::Expired,
status => status,
}
}
}

#[rasync_trait]
Expand Down Expand Up @@ -2185,6 +2236,8 @@ pub enum ProcessingChannelError {
FinalIncorrectPreimage,
#[error("The tlc forward fee is tow low")]
TlcForwardFeeIsTooLow,
#[error("The invoice status is invalid")]
FinalInvoiceInvalid(CkbInvoiceStatus),
#[error("The tlc number exceed limit of this channel")]
TlcNumberExceedLimit,
#[error("The tlc flight value exceed limit of this channel")]
Expand Down
12 changes: 12 additions & 0 deletions src/fiber/tests/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,18 @@ impl InvoiceStore for MemoryStore {
.get(hash)
.cloned()
}

fn get_invoice_status(&self, _id: &Hash256) -> Option<crate::invoice::CkbInvoiceStatus> {
unimplemented!()
}

fn update_invoice_status(
&self,
_id: &Hash256,
_status: crate::invoice::CkbInvoiceStatus,
) -> Result<(), InvoiceError> {
unimplemented!()
}
}

#[tokio::test]
Expand Down
2 changes: 2 additions & 0 deletions src/fiber/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1421,6 +1421,7 @@ pub enum TlcErrorCode {
ExpiryTooSoon = UPDATE | 14,
IncorrectOrUnknownPaymentDetails = PERM | 15,
InvoiceExpired = PERM | 16,
InvoiceCancelled = PERM | 17,
FinalIncorrectCltvExpiry = 18,
FinalIncorrectHtlcAmount = 19,
ChannelDisabled = UPDATE | 20,
Expand Down Expand Up @@ -1453,6 +1454,7 @@ impl TlcErrorCode {
| TlcErrorCode::FinalIncorrectCltvExpiry
| TlcErrorCode::FinalIncorrectHtlcAmount
| TlcErrorCode::InvoiceExpired
| TlcErrorCode::InvoiceCancelled
| TlcErrorCode::MppTimeout => true,
_ => false,
}
Expand Down
2 changes: 2 additions & 0 deletions src/invoice/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,6 @@ pub enum InvoiceError {
HexDecodeError(#[from] hex::FromHexError),
#[error("Duplicated inovice found: {0}")]
DuplicatedInvoice(String),
#[error("Invoice not found")]
InvoiceNotFound,
}
23 changes: 23 additions & 0 deletions src/invoice/invoice_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,36 @@ use secp256k1::{
ecdsa::{RecoverableSignature, RecoveryId},
Message, PublicKey, Secp256k1,
};
use std::fmt::Display;

use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use std::{cmp::Ordering, str::FromStr};

pub(crate) const SIGNATURE_U5_SIZE: usize = 104;

/// The currency of the invoice, can also used to represent the CKB network chain.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum CkbInvoiceStatus {
Open,
Cancelled,
Expired,
Received,
Paid,
}

impl Display for CkbInvoiceStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CkbInvoiceStatus::Open => write!(f, "Open"),
CkbInvoiceStatus::Cancelled => write!(f, "Cancelled"),
CkbInvoiceStatus::Expired => write!(f, "Expired"),
CkbInvoiceStatus::Received => write!(f, "Received"),
CkbInvoiceStatus::Paid => write!(f, "Paid"),
}
}
}

/// The currency of the invoice, can also used to represent the CKB network chain.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub enum Currency {
Expand Down
4 changes: 3 additions & 1 deletion src/invoice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ mod tests;

pub use command::*;
pub use errors::InvoiceError;
pub use invoice_impl::{Attribute, CkbInvoice, Currency, InvoiceBuilder, InvoiceSignature};
pub use invoice_impl::{
Attribute, CkbInvoice, CkbInvoiceStatus, Currency, InvoiceBuilder, InvoiceSignature,
};
pub use store::*;
9 changes: 7 additions & 2 deletions src/invoice/store.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::{CkbInvoiceStatus, InvoiceError};
use crate::{fiber::types::Hash256, invoice::CkbInvoice};

use super::InvoiceError;

pub trait InvoiceStore {
fn get_invoice(&self, id: &Hash256) -> Option<CkbInvoice>;
fn insert_invoice(
Expand All @@ -10,4 +9,10 @@ pub trait InvoiceStore {
preimage: Option<Hash256>,
) -> Result<(), InvoiceError>;
fn get_invoice_preimage(&self, id: &Hash256) -> Option<Hash256>;
fn update_invoice_status(
&self,
id: &Hash256,
status: CkbInvoiceStatus,
) -> Result<(), InvoiceError>;
fn get_invoice_status(&self, id: &Hash256) -> Option<CkbInvoiceStatus>;
}
97 changes: 69 additions & 28 deletions src/rpc/invoice.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::fiber::graph::{NetworkGraphStateStore, PaymentSessionStatus};
use crate::fiber::hash_algorithm::HashAlgorithm;
use crate::fiber::serde_utils::{U128Hex, U64Hex};
use crate::fiber::types::{Hash256, Privkey};
use crate::invoice::{CkbInvoice, Currency, InvoiceBuilder, InvoiceStore};
use crate::invoice::{CkbInvoice, CkbInvoiceStatus, Currency, InvoiceBuilder, InvoiceStore};
use crate::FiberConfig;
use ckb_jsonrpc_types::Script;
use jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE;
Expand Down Expand Up @@ -49,23 +48,15 @@ pub(crate) struct ParseInvoiceResult {
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GetInvoiceParams {
pub struct InvoiceParams {
payment_hash: Hash256,
}

#[derive(Clone, Serialize, Deserialize)]
enum InvoiceStatus {
Unpaid,
Inflight,
Paid,
Expired,
}

#[derive(Clone, Serialize, Deserialize)]
pub(crate) struct GetInvoiceResult {
invoice_address: String,
invoice: CkbInvoice,
status: InvoiceStatus,
status: CkbInvoiceStatus,
}

#[rpc(server)]
Expand All @@ -85,7 +76,13 @@ trait InvoiceRpc {
#[method(name = "get_invoice")]
async fn get_invoice(
&self,
payment_hash: GetInvoiceParams,
payment_hash: InvoiceParams,
) -> Result<GetInvoiceResult, ErrorObjectOwned>;

#[method(name = "cancel_invoice")]
async fn cancel_invoice(
&self,
payment_hash: InvoiceParams,
) -> Result<GetInvoiceResult, ErrorObjectOwned>;
}

Expand Down Expand Up @@ -117,7 +114,7 @@ impl<S> InvoiceRpcServerImpl<S> {
#[async_trait]
impl<S> InvoiceRpcServer for InvoiceRpcServerImpl<S>
where
S: InvoiceStore + NetworkGraphStateStore + Send + Sync + 'static,
S: InvoiceStore + Send + Sync + 'static,
{
async fn new_invoice(
&self,
Expand Down Expand Up @@ -196,25 +193,20 @@ where

async fn get_invoice(
&self,
params: GetInvoiceParams,
params: InvoiceParams,
) -> Result<GetInvoiceResult, ErrorObjectOwned> {
let payment_hash = params.payment_hash;
match self.store.get_invoice(&payment_hash) {
Some(invoice) => {
let invoice_status = if invoice.is_expired() {
InvoiceStatus::Expired
} else {
InvoiceStatus::Unpaid
};
let payment_session = self.store.get_payment_session(payment_hash);
let status = match payment_session {
Some(session) => match session.status {
PaymentSessionStatus::Inflight => InvoiceStatus::Inflight,
PaymentSessionStatus::Success => InvoiceStatus::Paid,
_ => invoice_status,
},
None => invoice_status,
let status = match self
.store
.get_invoice_status(&payment_hash)
.expect("no invoice status found")
{
CkbInvoiceStatus::Open if invoice.is_expired() => CkbInvoiceStatus::Expired,
status => status,
};

Ok(GetInvoiceResult {
invoice_address: invoice.to_string(),
invoice,
Expand All @@ -228,4 +220,53 @@ where
)),
}
}

async fn cancel_invoice(
&self,
params: InvoiceParams,
) -> Result<GetInvoiceResult, ErrorObjectOwned> {
let payment_hash = params.payment_hash;
match self.store.get_invoice(&payment_hash) {
Some(invoice) => {
let status = match self
.store
.get_invoice_status(&payment_hash)
.expect("no invoice status found")
{
CkbInvoiceStatus::Open if invoice.is_expired() => CkbInvoiceStatus::Expired,
status => status,
};

let new_status = match status {
CkbInvoiceStatus::Paid | CkbInvoiceStatus::Cancelled => {
return Err(ErrorObjectOwned::owned(
CALL_EXECUTION_FAILED_CODE,
format!("invoice can not be canceled, current status: {}", status),
Some(payment_hash),
));
}
_ => CkbInvoiceStatus::Cancelled,
};
self.store
.update_invoice_status(&payment_hash, new_status)
.map_err(|e| {
ErrorObjectOwned::owned(
CALL_EXECUTION_FAILED_CODE,
e.to_string(),
Some(payment_hash),
)
})?;
Ok(GetInvoiceResult {
invoice_address: invoice.to_string(),
invoice,
status: new_status,
})
}
None => Err(ErrorObjectOwned::owned(
CALL_EXECUTION_FAILED_CODE,
"invoice not found".to_string(),
Some(payment_hash),
)),
}
}
}
Loading
Loading