From 8d45f215178aa92eb1628c7a4b4ef3ce002b8afb Mon Sep 17 00:00:00 2001 From: michael1011 Date: Mon, 30 Dec 2024 01:21:12 +0100 Subject: [PATCH] feat: add settled_at to invoices --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- .../2024-12-29-234029_settled_at/down.sql | 2 ++ migrations/2024-12-29-234029_settled_at/up.sql | 2 ++ .../2024-12-29-234029_settled_at/down.sql | 2 ++ .../2024-12-29-234029_settled_at/up.sql | 2 ++ protos/hold.proto | 2 ++ src/commands/list.rs | 3 +++ src/database/helpers/invoice_helper.rs | 18 ++++++++++++++---- src/database/model.rs | 3 +++ src/database/schema.rs | 1 + src/grpc/transformers.rs | 4 ++++ src/handler.rs | 16 +++++++++++----- src/main.rs | 2 -- tests-regtest/hold/regtest_grpc.py | 1 + tests-regtest/hold/regtest_rpc.py | 3 ++- 16 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 migrations/2024-12-29-234029_settled_at/down.sql create mode 100644 migrations/2024-12-29-234029_settled_at/up.sql create mode 100644 migrations_postgres/2024-12-29-234029_settled_at/down.sql create mode 100644 migrations_postgres/2024-12-29-234029_settled_at/up.sql diff --git a/Cargo.lock b/Cargo.lock index 91b9b8a..54126d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1816,18 +1816,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 86ebe28..23cf3b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ prost = "0.13.4" rcgen = { version = "0.13.2", features = ["x509-parser"] } tokio = { version = "1.42.0", features = ["macros", "rt-multi-thread", "sync"] } tonic = { version = "0.12.3", features = ["prost", "tls", "gzip", "zstd"] } -serde = { version = "1.0.216", features = ["derive"] } +serde = { version = "1.0.217", features = ["derive"] } serde_json = { version = "1.0.134", features = ["preserve_order"] } lightning-invoice = { version = "0.32.0", features = ["std"] } chrono = { version = "0.4.39", features = ["serde"] } diff --git a/migrations/2024-12-29-234029_settled_at/down.sql b/migrations/2024-12-29-234029_settled_at/down.sql new file mode 100644 index 0000000..52a9def --- /dev/null +++ b/migrations/2024-12-29-234029_settled_at/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE invoices + DROP COLUMN settled_at; diff --git a/migrations/2024-12-29-234029_settled_at/up.sql b/migrations/2024-12-29-234029_settled_at/up.sql new file mode 100644 index 0000000..c690c08 --- /dev/null +++ b/migrations/2024-12-29-234029_settled_at/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE invoices + ADD COLUMN settled_at TIMESTAMP; diff --git a/migrations_postgres/2024-12-29-234029_settled_at/down.sql b/migrations_postgres/2024-12-29-234029_settled_at/down.sql new file mode 100644 index 0000000..52a9def --- /dev/null +++ b/migrations_postgres/2024-12-29-234029_settled_at/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE invoices + DROP COLUMN settled_at; diff --git a/migrations_postgres/2024-12-29-234029_settled_at/up.sql b/migrations_postgres/2024-12-29-234029_settled_at/up.sql new file mode 100644 index 0000000..c690c08 --- /dev/null +++ b/migrations_postgres/2024-12-29-234029_settled_at/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE invoices + ADD COLUMN settled_at TIMESTAMP; diff --git a/protos/hold.proto b/protos/hold.proto index 5e94fe1..c76f6db 100644 --- a/protos/hold.proto +++ b/protos/hold.proto @@ -89,6 +89,8 @@ message Invoice { string bolt11 = 4; InvoiceState state = 5; uint64 created_at = 6; + optional uint64 settled_at = 8; + repeated Htlc htlcs = 7; } diff --git a/src/commands/list.rs b/src/commands/list.rs index 17c2a2b..5d5bb5a 100644 --- a/src/commands/list.rs +++ b/src/commands/list.rs @@ -43,6 +43,8 @@ struct PrettyHoldInvoice { pub bolt11: String, pub state: String, pub created_at: chrono::NaiveDateTime, + #[serde(skip_serializing_if = "Option::is_none")] + pub settled_at: Option, pub htlcs: Vec, } @@ -55,6 +57,7 @@ impl From for PrettyHoldInvoice { bolt11: value.invoice.bolt11.clone(), state: value.invoice.state.clone(), created_at: value.invoice.created_at, + settled_at: value.invoice.settled_at, htlcs: value.htlcs.clone(), } } diff --git a/src/database/helpers/invoice_helper.rs b/src/database/helpers/invoice_helper.rs index dab03ca..53fb031 100644 --- a/src/database/helpers/invoice_helper.rs +++ b/src/database/helpers/invoice_helper.rs @@ -76,10 +76,20 @@ impl InvoiceHelper for InvoiceHelperDatabase { ) -> Result { state.validate_transition(new_state)?; - Ok(update(invoices::dsl::invoices) - .filter(invoices::dsl::id.eq(id)) - .set(invoices::dsl::state.eq(new_state.to_string())) - .execute(&mut self.pool.get()?)?) + if new_state != InvoiceState::Paid { + Ok(update(invoices::dsl::invoices) + .filter(invoices::dsl::id.eq(id)) + .set(invoices::dsl::state.eq(new_state.to_string())) + .execute(&mut self.pool.get()?)?) + } else { + Ok(update(invoices::dsl::invoices) + .filter(invoices::dsl::id.eq(id)) + .set(( + invoices::dsl::state.eq(new_state.to_string()), + invoices::dsl::settled_at.eq(Some(Utc::now().naive_utc())), + )) + .execute(&mut self.pool.get()?)?) + } } fn set_invoice_preimage(&self, id: i64, preimage: &[u8]) -> Result { diff --git a/src/database/model.rs b/src/database/model.rs index fbf40ec..2674c03 100644 --- a/src/database/model.rs +++ b/src/database/model.rs @@ -13,6 +13,7 @@ pub struct Invoice { pub bolt11: String, pub state: String, pub created_at: chrono::NaiveDateTime, + pub settled_at: Option, } #[derive(Insertable, Debug, PartialEq, Clone)] @@ -358,6 +359,7 @@ mod test { bolt11: "".to_string(), state: "".to_string(), created_at: Default::default(), + settled_at: None, }, vec![], ); @@ -407,6 +409,7 @@ mod test { bolt11: "".to_string(), state: "".to_string(), created_at: Default::default(), + settled_at: None, }, vec![ Htlc { diff --git a/src/database/schema.rs b/src/database/schema.rs index cedeebc..bcd5ecd 100644 --- a/src/database/schema.rs +++ b/src/database/schema.rs @@ -6,6 +6,7 @@ diesel::table! { bolt11 -> Text, state -> Text, created_at -> Timestamp, + settled_at -> Nullable, } } diff --git a/src/grpc/transformers.rs b/src/grpc/transformers.rs index 19a8767..04d33d8 100644 --- a/src/grpc/transformers.rs +++ b/src/grpc/transformers.rs @@ -27,6 +27,10 @@ impl From for hold::Invoice { InvoiceState::try_from(value.invoice.state.as_str()).unwrap(), ), created_at: value.invoice.created_at.and_utc().timestamp() as u64, + settled_at: value + .invoice + .settled_at + .map(|t| t.and_utc().timestamp() as u64), htlcs: value.htlcs.into_iter().map(|htlc| htlc.into()).collect(), } } diff --git a/src/handler.rs b/src/handler.rs index 61fb35e..8d0e65e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -299,11 +299,12 @@ mod test { Ok(Some(HoldInvoice { invoice: Invoice { id: 0, - payment_hash: vec![], preimage: None, + settled_at: None, + payment_hash: vec![], bolt11: "".to_string(), - state: InvoiceState::Paid.to_string(), created_at: Default::default(), + state: InvoiceState::Paid.to_string(), }, htlcs: vec![], })) @@ -349,8 +350,9 @@ mod test { Ok(Some(HoldInvoice { invoice: Invoice { id: 0, - payment_hash: vec![], preimage: None, + settled_at: None, + payment_hash: vec![], bolt11: INVOICE.to_string(), state: InvoiceState::Unpaid.to_string(), created_at: Default::default(), @@ -408,8 +410,9 @@ mod test { Ok(Some(HoldInvoice { invoice: Invoice { id: 0, - payment_hash: vec![], preimage: None, + settled_at: None, + payment_hash: vec![], bolt11: INVOICE.to_string(), state: InvoiceState::Unpaid.to_string(), created_at: Default::default(), @@ -470,8 +473,9 @@ mod test { Ok(Some(HoldInvoice { invoice: Invoice { id: 0, - payment_hash: vec![], preimage: None, + settled_at: None, + payment_hash: vec![], bolt11: INVOICE.to_string(), state: InvoiceState::Unpaid.to_string(), created_at: Default::default(), @@ -537,6 +541,7 @@ mod test { invoice: Invoice { id: 0, preimage: None, + settled_at: None, bolt11: INVOICE.to_string(), created_at: Default::default(), payment_hash: payment_hash_cp.clone(), @@ -557,6 +562,7 @@ mod test { invoice: Invoice { id: 0, preimage: None, + settled_at: None, bolt11: INVOICE.to_string(), created_at: Default::default(), state: InvoiceState::Unpaid.to_string(), diff --git a/src/main.rs b/src/main.rs index c9e6ef6..4093c69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,8 +27,6 @@ struct State { invoice_helper: T, } -// TODO: backfill records from old datastore - #[tokio::main] async fn main() -> Result<()> { std::env::set_var( diff --git a/tests-regtest/hold/regtest_grpc.py b/tests-regtest/hold/regtest_grpc.py index f964ab1..f63fd45 100644 --- a/tests-regtest/hold/regtest_grpc.py +++ b/tests-regtest/hold/regtest_grpc.py @@ -305,6 +305,7 @@ def track_states() -> list[InvoiceState]: invoice_state = cl.List(ListRequest(payment_hash=payment_hash)).invoices[0] assert invoice_state.state == InvoiceState.PAID + assert invoice_state.settled_at - int(time_now().timestamp()) < 2 assert len(invoice_state.htlcs) == 1 assert invoice_state.htlcs[0].state == InvoiceState.PAID diff --git a/tests-regtest/hold/regtest_rpc.py b/tests-regtest/hold/regtest_rpc.py index 28dc396..e264154 100644 --- a/tests-regtest/hold/regtest_rpc.py +++ b/tests-regtest/hold/regtest_rpc.py @@ -3,7 +3,7 @@ import time from typing import Any -from hold.utils import LndPay, lightning, new_preimage +from hold.utils import LndPay, lightning, new_preimage, time_now def check_unpaid_invoice( @@ -84,6 +84,7 @@ def test_settle(self) -> None: data = lightning("listholdinvoices", payment_hash)["holdinvoices"][0] assert data["state"] == "paid" + assert data["settled_at"].startswith(time_now().strftime("%Y-%m-%dT%H:%M")) htlcs = data["htlcs"] assert len(htlcs) == 1