From 6b2f0f6e1ca3b58c8222acafb6d121cd25e3ae41 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 17 Oct 2024 11:20:36 +0800 Subject: [PATCH 1/4] add invoice status and check logic --- src/fiber/channel.rs | 67 +++++++++++++++++++++++++++++------ src/fiber/tests/test_utils.rs | 12 +++++++ src/fiber/types.rs | 2 ++ src/invoice/errors.rs | 2 ++ src/invoice/invoice_impl.rs | 10 ++++++ src/invoice/mod.rs | 4 ++- src/invoice/store.rs | 9 +++-- src/rpc/invoice.rs | 36 ++++++------------- src/store.rs | 47 ++++++++++++++++++++++-- src/tests/store.rs | 7 ++++ 10 files changed, 155 insertions(+), 41 deletions(-) diff --git a/src/fiber/channel.rs b/src/fiber/channel.rs index 41c6771f..38273332 100644 --- a/src/fiber/channel.rs +++ b/src/fiber/channel.rs @@ -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; @@ -727,18 +727,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; + } } } @@ -761,6 +776,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; } @@ -779,6 +799,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> = None; + let mut update_invoice_payment_hash: Option = 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 @@ -814,6 +835,14 @@ where return Err(ProcessingChannelError::FinalIncorrectHTLCAmount); } + let payment_hash = add_tlc.payment_hash; + if let Some(invoice) = self.store.get_invoice(&payment_hash) { + if self.get_invoice_status(&invoice) != CkbInvoiceStatus::Open { + return Err(ProcessingChannelError::FinalInvoiceInvalid); + } + 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. @@ -851,6 +880,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 @@ -1500,6 +1534,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] @@ -2183,6 +2228,8 @@ pub enum ProcessingChannelError { FinalIncorrectPreimage, #[error("The tlc forward fee is tow low")] TlcForwardFeeIsTooLow, + #[error("The invoice status is invalid")] + FinalInvoiceInvalid, #[error("The tlc number exceed limit of this channel")] TlcNumberExceedLimit, #[error("The tlc flight value exceed limit of this channel")] diff --git a/src/fiber/tests/test_utils.rs b/src/fiber/tests/test_utils.rs index 2bd05d99..2b347deb 100644 --- a/src/fiber/tests/test_utils.rs +++ b/src/fiber/tests/test_utils.rs @@ -670,6 +670,18 @@ impl InvoiceStore for MemoryStore { .get(hash) .cloned() } + + fn get_invoice_status(&self, _id: &Hash256) -> Option { + unimplemented!() + } + + fn update_invoice_status( + &self, + _id: &Hash256, + _status: crate::invoice::CkbInvoiceStatus, + ) -> Result<(), InvoiceError> { + unimplemented!() + } } #[tokio::test] diff --git a/src/fiber/types.rs b/src/fiber/types.rs index 096cec2a..e24a167d 100644 --- a/src/fiber/types.rs +++ b/src/fiber/types.rs @@ -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, @@ -1453,6 +1454,7 @@ impl TlcErrorCode { | TlcErrorCode::FinalIncorrectCltvExpiry | TlcErrorCode::FinalIncorrectHtlcAmount | TlcErrorCode::InvoiceExpired + | TlcErrorCode::InvoiceCancelled | TlcErrorCode::MppTimeout => true, _ => false, } diff --git a/src/invoice/errors.rs b/src/invoice/errors.rs index 337658d7..1c16bdf1 100644 --- a/src/invoice/errors.rs +++ b/src/invoice/errors.rs @@ -55,4 +55,6 @@ pub enum InvoiceError { HexDecodeError(#[from] hex::FromHexError), #[error("Duplicated inovice found: {0}")] DuplicatedInvoice(String), + #[error("Invoice not found")] + InvoiceNotFound, } diff --git a/src/invoice/invoice_impl.rs b/src/invoice/invoice_impl.rs index 135c7115..e12a9726 100644 --- a/src/invoice/invoice_impl.rs +++ b/src/invoice/invoice_impl.rs @@ -26,6 +26,16 @@ 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, +} + /// 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 { diff --git a/src/invoice/mod.rs b/src/invoice/mod.rs index 5ac6b7ba..625216c7 100644 --- a/src/invoice/mod.rs +++ b/src/invoice/mod.rs @@ -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::*; diff --git a/src/invoice/store.rs b/src/invoice/store.rs index 14e56670..e2018e01 100644 --- a/src/invoice/store.rs +++ b/src/invoice/store.rs @@ -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; fn insert_invoice( @@ -10,4 +9,10 @@ pub trait InvoiceStore { preimage: Option, ) -> Result<(), InvoiceError>; fn get_invoice_preimage(&self, id: &Hash256) -> Option; + fn update_invoice_status( + &self, + id: &Hash256, + status: CkbInvoiceStatus, + ) -> Result<(), InvoiceError>; + fn get_invoice_status(&self, id: &Hash256) -> Option; } diff --git a/src/rpc/invoice.rs b/src/rpc/invoice.rs index cdf64636..1d190c5b 100644 --- a/src/rpc/invoice.rs +++ b/src/rpc/invoice.rs @@ -1,10 +1,9 @@ use std::time::Duration; -use crate::fiber::graph::{NetworkGraphStateStore, PaymentSessionStatus}; use crate::fiber::hash_algorithm::HashAlgorithm; use crate::fiber::serde_utils::{U128Hex, U64Hex}; use crate::fiber::types::Hash256; -use crate::invoice::{CkbInvoice, Currency, InvoiceBuilder, InvoiceStore}; +use crate::invoice::{CkbInvoice, CkbInvoiceStatus, Currency, InvoiceBuilder, InvoiceStore}; use ckb_jsonrpc_types::Script; use jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE; use jsonrpsee::{core::async_trait, proc_macros::rpc, types::ErrorObjectOwned}; @@ -53,19 +52,11 @@ pub struct GetInvoiceParams { 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)] @@ -103,7 +94,7 @@ impl InvoiceRpcServerImpl { #[async_trait] impl InvoiceRpcServer for InvoiceRpcServerImpl where - S: InvoiceStore + NetworkGraphStateStore + Send + Sync + 'static, + S: InvoiceStore + Send + Sync + 'static, { async fn new_invoice( &self, @@ -185,20 +176,15 @@ where 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, diff --git a/src/store.rs b/src/store.rs index 31f73e1e..de411c32 100644 --- a/src/store.rs +++ b/src/store.rs @@ -4,7 +4,7 @@ use crate::{ graph::{ChannelInfo, NetworkGraphStateStore, NodeInfo, PaymentSession}, types::{Hash256, Pubkey}, }, - invoice::{CkbInvoice, InvoiceError, InvoiceStore}, + invoice::{CkbInvoice, CkbInvoiceStatus, InvoiceError, InvoiceStore}, watchtower::{ChannelData, RevocationData, WatchtowerStore}, }; use ckb_jsonrpc_types::JsonBytes; @@ -88,6 +88,13 @@ impl Batch { serde_json::to_vec(&preimage).expect("serialize Hash256 should be OK"), ); } + KeyValue::CkbInvoiceStatus(id, status) => { + let key = [&[CKB_INVOICE_STATUS_PREFIX], id.as_ref()].concat(); + self.put( + key, + serde_json::to_vec(&status).expect("serialize CkbInvoiceStatus should be OK"), + ); + } KeyValue::PeerIdChannelId((peer_id, channel_id), state) => { let key = [ &[PEER_ID_CHANNEL_ID_PREFIX], @@ -192,7 +199,9 @@ impl Batch { /// | KeyPrefix:: | Key:: | Value:: | /// +--------------+--------------------+--------------------------+ /// | 0 | Hash256 | ChannelActorState | -/// | 32 | Hash256 | CkbInvoice | +/// | 32 | Payment_hash | CkbInvoice | +/// | 33 | Payment_hash | CkbInvoice Preimage | +/// | 34 | Payment_hash | CkbInvoice Status | /// | 64 | PeerId | Hash256 | ChannelState | /// | 96 | ChannelId | ChannelInfo | /// | 97 | Block | Index | ChannelId | @@ -208,6 +217,7 @@ impl Batch { const CHANNEL_ACTOR_STATE_PREFIX: u8 = 0; const CKB_INVOICE_PREFIX: u8 = 32; const CKB_INVOICE_PREIMAGE_PREFIX: u8 = 33; +const CKB_INVOICE_STATUS_PREFIX: u8 = 34; const PEER_ID_CHANNEL_ID_PREFIX: u8 = 64; pub(crate) const CHANNEL_INFO_PREFIX: u8 = 96; const CHANNEL_ANNOUNCEMENT_INDEX_PREFIX: u8 = 97; @@ -222,6 +232,7 @@ enum KeyValue { ChannelActorState(Hash256, ChannelActorState), CkbInvoice(Hash256, CkbInvoice), CkbInvoicePreimage(Hash256, Hash256), + CkbInvoiceStatus(Hash256, CkbInvoiceStatus), PeerIdChannelId((PeerId, Hash256), ChannelState), PeerIdMultiAddr(PeerId, Multiaddr), NodeInfo(Pubkey, NodeInfo), @@ -330,7 +341,12 @@ impl InvoiceStore for Store { if let Some(preimage) = preimage { batch.put_kv(KeyValue::CkbInvoicePreimage(*hash, preimage)); } - batch.put_kv(KeyValue::CkbInvoice(*invoice.payment_hash(), invoice)); + let payment_hash = *invoice.payment_hash(); + batch.put_kv(KeyValue::CkbInvoice(payment_hash, invoice)); + batch.put_kv(KeyValue::CkbInvoiceStatus( + payment_hash, + CkbInvoiceStatus::Open, + )); batch.commit(); return Ok(()); } @@ -343,6 +359,31 @@ impl InvoiceStore for Store { self.get(key) .map(|v| serde_json::from_slice(v.as_ref()).expect("deserialize Hash256 should be OK")) } + + fn update_invoice_status( + &self, + id: &Hash256, + status: crate::invoice::CkbInvoiceStatus, + ) -> Result<(), InvoiceError> { + let _invoice = self.get_invoice(id).ok_or(InvoiceError::InvoiceNotFound)?; + let mut key = Vec::with_capacity(33); + key.extend_from_slice(&[CKB_INVOICE_STATUS_PREFIX]); + key.extend_from_slice(id.as_ref()); + let mut batch = self.batch(); + batch.put_kv(KeyValue::CkbInvoiceStatus(*id, status)); + batch.commit(); + Ok(()) + } + + fn get_invoice_status(&self, id: &Hash256) -> Option { + let mut key = Vec::with_capacity(33); + key.extend_from_slice(&[CKB_INVOICE_STATUS_PREFIX]); + key.extend_from_slice(id.as_ref()); + + self.get(key).map(|v| { + serde_json::from_slice(v.as_ref()).expect("deserialize CkbInvoiceStatus should be OK") + }) + } } impl NetworkGraphStateStore for Store { diff --git a/src/tests/store.rs b/src/tests/store.rs index 02f0715e..8ab9be12 100644 --- a/src/tests/store.rs +++ b/src/tests/store.rs @@ -98,6 +98,13 @@ fn test_store_invoice() { let invalid_hash = gen_sha256_hash(); assert_eq!(store.get_invoice_preimage(&invalid_hash), None); + + assert_eq!(store.get_invoice_status(hash), Some(CkbInvoiceStatus::Open)); + assert_eq!(store.get_invoice_status(&gen_sha256_hash()), None); + + let status = CkbInvoiceStatus::Paid; + store.update_invoice_status(hash, status).unwrap(); + assert_eq!(store.get_invoice_status(hash), Some(status)); } #[test] From ad39d1c6ef21cd542c649716a41d5c2f713e9f66 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 17 Oct 2024 11:39:18 +0800 Subject: [PATCH 2/4] add invoice cancel rpc --- src/invoice/invoice_impl.rs | 13 ++++ src/rpc/invoice.rs | 61 ++++++++++++++++++- tests/bruno/e2e/invoice-ops/1-gen-invoice.bru | 3 +- .../invoice-ops/2-gen-invoice-duplicate.bru | 4 +- .../e2e/invoice-ops/3-invoice-decode.bru | 2 +- tests/bruno/e2e/invoice-ops/4-get-invoice.bru | 40 ++++++++++++ .../e2e/invoice-ops/5-cancel-invoice.bru | 43 +++++++++++++ 7 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 tests/bruno/e2e/invoice-ops/4-get-invoice.bru create mode 100644 tests/bruno/e2e/invoice-ops/5-cancel-invoice.bru diff --git a/src/invoice/invoice_impl.rs b/src/invoice/invoice_impl.rs index e12a9726..535df5a8 100644 --- a/src/invoice/invoice_impl.rs +++ b/src/invoice/invoice_impl.rs @@ -19,6 +19,7 @@ use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, Message, PublicKey, Secp256k1, }; +use std::fmt::Display; use serde::{Deserialize, Serialize}; use serde_with::serde_as; @@ -36,6 +37,18 @@ pub enum CkbInvoiceStatus { 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 { diff --git a/src/rpc/invoice.rs b/src/rpc/invoice.rs index 1d190c5b..e03005e0 100644 --- a/src/rpc/invoice.rs +++ b/src/rpc/invoice.rs @@ -48,7 +48,7 @@ pub(crate) struct ParseInvoiceResult { } #[derive(Serialize, Deserialize, Debug)] -pub struct GetInvoiceParams { +pub struct InvoiceParams { payment_hash: Hash256, } @@ -76,7 +76,13 @@ trait InvoiceRpc { #[method(name = "get_invoice")] async fn get_invoice( &self, - payment_hash: GetInvoiceParams, + payment_hash: InvoiceParams, + ) -> Result; + + #[method(name = "cancel_invoice")] + async fn cancel_invoice( + &self, + payment_hash: InvoiceParams, ) -> Result; } @@ -171,7 +177,7 @@ where async fn get_invoice( &self, - params: GetInvoiceParams, + params: InvoiceParams, ) -> Result { let payment_hash = params.payment_hash; match self.store.get_invoice(&payment_hash) { @@ -198,4 +204,53 @@ where )), } } + + async fn cancel_invoice( + &self, + params: InvoiceParams, + ) -> Result { + 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), + )), + } + } } diff --git a/tests/bruno/e2e/invoice-ops/1-gen-invoice.bru b/tests/bruno/e2e/invoice-ops/1-gen-invoice.bru index cacace8e..3a3ca6f8 100644 --- a/tests/bruno/e2e/invoice-ops/1-gen-invoice.bru +++ b/tests/bruno/e2e/invoice-ops/1-gen-invoice.bru @@ -56,6 +56,5 @@ script:post-response { await new Promise(r => setTimeout(r, 100)); console.log("generated result: ", res.body.result); bru.setVar("INVOICE_ADDR", res.body.result.invoice_address); - - + bru.setVar("INVOICE_PAYMENT_HASH", res.body.result.invoice.data.payment_hash); } diff --git a/tests/bruno/e2e/invoice-ops/2-gen-invoice-duplicate.bru b/tests/bruno/e2e/invoice-ops/2-gen-invoice-duplicate.bru index a77e7c73..837f436c 100644 --- a/tests/bruno/e2e/invoice-ops/2-gen-invoice-duplicate.bru +++ b/tests/bruno/e2e/invoice-ops/2-gen-invoice-duplicate.bru @@ -1,7 +1,7 @@ meta { name: generate duplicate invoice type: http - seq: 3 + seq: 2 } post { @@ -16,7 +16,7 @@ headers { } docs { - description: "use the same payment_preimage to generate a new invoice" + description: "use the same payment_preimage to generate a new invoice, expect an error" } body:json { diff --git a/tests/bruno/e2e/invoice-ops/3-invoice-decode.bru b/tests/bruno/e2e/invoice-ops/3-invoice-decode.bru index f6b1ae4e..cf2bf00b 100644 --- a/tests/bruno/e2e/invoice-ops/3-invoice-decode.bru +++ b/tests/bruno/e2e/invoice-ops/3-invoice-decode.bru @@ -1,7 +1,7 @@ meta { name: invoice decode test type: http - seq: 5 + seq: 3 } post { diff --git a/tests/bruno/e2e/invoice-ops/4-get-invoice.bru b/tests/bruno/e2e/invoice-ops/4-get-invoice.bru new file mode 100644 index 00000000..8bfefa87 --- /dev/null +++ b/tests/bruno/e2e/invoice-ops/4-get-invoice.bru @@ -0,0 +1,40 @@ +meta { + name: get invoice + type: http + seq: 4 +} + +post { + url: {{NODE3_RPC_URL}} + body: json + auth: none +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "id": "42", + "jsonrpc": "2.0", + "method": "get_invoice", + "params": [ + { + "payment_hash": "{{INVOICE_PAYMENT_HASH}}" + } + ] + } +} + +assert { + res.body.error: isUndefined + res.body.result: isDefined +} + +script:post-response { + // Sleep for sometime to make sure current operation finishes before next request starts. + await new Promise(r => setTimeout(r, 100)); + console.log("generated result: ", res.body); +} diff --git a/tests/bruno/e2e/invoice-ops/5-cancel-invoice.bru b/tests/bruno/e2e/invoice-ops/5-cancel-invoice.bru new file mode 100644 index 00000000..a5b9182d --- /dev/null +++ b/tests/bruno/e2e/invoice-ops/5-cancel-invoice.bru @@ -0,0 +1,43 @@ +meta { + name: cancel invoice + type: http + seq: 5 +} + +post { + url: {{NODE3_RPC_URL}} + body: json + auth: none +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "id": "42", + "jsonrpc": "2.0", + "method": "cancel_invoice", + "params": [ + { + "payment_hash": "{{INVOICE_PAYMENT_HASH}}" + } + ] + } +} + +assert { + res.body.error: isUndefined + res.body.result: isDefined +} + +script:post-response { + // Sleep for sometime to make sure current operation finishes before next request starts. + await new Promise(r => setTimeout(r, 100)); + console.log("generated result: ", res.body); + if (res.body.result.status != "Cancelled") { + throw new Error("Assertion failed: status expected to be Cancelled"); + } +} From 8167d249521892e67dcd602613f9a1a74750250f Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 17 Oct 2024 11:53:32 +0800 Subject: [PATCH 3/4] add e2e test for invoice status --- .../3-nodes-transfer/14-node3-get-invoice.bru | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 tests/bruno/e2e/3-nodes-transfer/14-node3-get-invoice.bru diff --git a/tests/bruno/e2e/3-nodes-transfer/14-node3-get-invoice.bru b/tests/bruno/e2e/3-nodes-transfer/14-node3-get-invoice.bru new file mode 100644 index 00000000..4f80dcca --- /dev/null +++ b/tests/bruno/e2e/3-nodes-transfer/14-node3-get-invoice.bru @@ -0,0 +1,43 @@ +meta { + name: get invoice + type: http + seq: 14 +} + +post { + url: {{NODE3_RPC_URL}} + body: json + auth: none +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "id": "42", + "jsonrpc": "2.0", + "method": "get_invoice", + "params": [ + { + "payment_hash": "{{payment_hash}}" + } + ] + } +} + +assert { + res.body.error: isUndefined + res.body.result: isDefined +} + +script:post-response { + // Sleep for sometime to make sure current operation finishes before next request starts. + await new Promise(r => setTimeout(r, 100)); + console.log("generated result: ", res.body); + if (res.body.result.status != "Paid") { + throw new Error("Assertion failed: status expected to be Paid"); + } +} From 022e5e5c639a9ba58d424cfd14cbdc8b8c3d8f7e Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 17 Oct 2024 12:01:29 +0800 Subject: [PATCH 4/4] fix invoice status error --- src/fiber/channel.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/fiber/channel.rs b/src/fiber/channel.rs index 38273332..629d3c70 100644 --- a/src/fiber/channel.rs +++ b/src/fiber/channel.rs @@ -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 @@ -837,8 +842,9 @@ where let payment_hash = add_tlc.payment_hash; if let Some(invoice) = self.store.get_invoice(&payment_hash) { - if self.get_invoice_status(&invoice) != CkbInvoiceStatus::Open { - return Err(ProcessingChannelError::FinalInvoiceInvalid); + 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); } @@ -2229,7 +2235,7 @@ pub enum ProcessingChannelError { #[error("The tlc forward fee is tow low")] TlcForwardFeeIsTooLow, #[error("The invoice status is invalid")] - FinalInvoiceInvalid, + FinalInvoiceInvalid(CkbInvoiceStatus), #[error("The tlc number exceed limit of this channel")] TlcNumberExceedLimit, #[error("The tlc flight value exceed limit of this channel")]