mirror of
https://github.com/ACINQ/eclair.git
synced 2025-03-12 19:01:39 +01:00
Add listreceivedpayments
RPC call (#2607)
Add a new RPC to list payments received by the node.
This commit is contained in:
parent
dcedeccb05
commit
e383d81de8
11 changed files with 46 additions and 31 deletions
|
@ -21,7 +21,7 @@ _eclair-cli()
|
|||
*)
|
||||
# works fine, but is too slow at the moment.
|
||||
# allopts=$($eclaircli help 2>&1 | awk '$1 ~ /^"/ { sub(/,/, ""); print $1}' | sed 's/[":]//g')
|
||||
allopts="getinfo connect open close forceclose updaterelayfee peers channels channel allnodes allchannels allupdates findroute findroutetonode findroutebetweennodes parseinvoice payinvoice sendtonode getsentinfo createinvoice getinvoice listinvoices listpendinginvoices getreceivedinfo audit networkfees channelstats"
|
||||
allopts="getinfo connect open close forceclose updaterelayfee peers channels channel allnodes allchannels allupdates findroute findroutetonode findroutebetweennodes parseinvoice payinvoice sendtonode getsentinfo createinvoice getinvoice listinvoices listpendinginvoices listreceivedpayments getreceivedinfo audit networkfees channelstats"
|
||||
|
||||
if ! [[ " $allopts " =~ " $prev " ]]; then # prevent double arguments
|
||||
if [[ -z "$cur" || "$cur" =~ ^[a-z] ]]; then
|
||||
|
|
|
@ -38,6 +38,7 @@ All this data is signed and encrypted so that it can not be read or forged by th
|
|||
- `channel-created` is a new websocket event that is published when a channel's funding transaction has been broadcast (#2567)
|
||||
- `channel-opened` websocket event was updated to contain the final `channel_id` and be published when a channel is ready to process payments (#2567)
|
||||
- `getsentinfo` can now be used with `--offer` to list payments sent to a specific offer.
|
||||
- `listreceivedpayments` lists payments received by your node (#2607)
|
||||
|
||||
### Miscellaneous improvements and bug fixes
|
||||
|
||||
|
|
|
@ -113,6 +113,8 @@ trait Eclair {
|
|||
|
||||
def receivedInfo(paymentHash: ByteVector32)(implicit timeout: Timeout): Future[Option[IncomingPayment]]
|
||||
|
||||
def receivedPayments(from: TimestampSecond, to: TimestampSecond, paginated_opt: Option[Paginated])(implicit timeout: Timeout): Future[Seq[IncomingPayment]]
|
||||
|
||||
def send(externalId_opt: Option[String], amount: MilliSatoshi, invoice: Bolt11Invoice, maxAttempts_opt: Option[Int] = None, maxFeeFlat_opt: Option[Satoshi] = None, maxFeePct_opt: Option[Double] = None, pathFindingExperimentName_opt: Option[String] = None)(implicit timeout: Timeout): Future[UUID]
|
||||
|
||||
def sendBlocking(externalId_opt: Option[String], amount: MilliSatoshi, invoice: Bolt11Invoice, maxAttempts_opt: Option[Int] = None, maxFeeFlat_opt: Option[Satoshi] = None, maxFeePct_opt: Option[Double] = None, pathFindingExperimentName_opt: Option[String] = None)(implicit timeout: Timeout): Future[PaymentEvent]
|
||||
|
@ -460,6 +462,10 @@ class EclairImpl(appKit: Kit) extends Eclair with Logging {
|
|||
appKit.nodeParams.db.payments.listPendingIncomingPayments(from.toTimestampMilli, to.toTimestampMilli, paginated_opt).map(_.invoice)
|
||||
}
|
||||
|
||||
override def receivedPayments(from: TimestampSecond, to: TimestampSecond, paginated_opt: Option[Paginated])(implicit timeout: Timeout): Future[Seq[IncomingPayment]] = Future {
|
||||
appKit.nodeParams.db.payments.listReceivedIncomingPayments(from.toTimestampMilli, to.toTimestampMilli, paginated_opt)
|
||||
}
|
||||
|
||||
override def getInvoice(paymentHash: ByteVector32)(implicit timeout: Timeout): Future[Option[Invoice]] = Future {
|
||||
appKit.nodeParams.db.payments.getIncomingPayment(paymentHash).map(_.invoice)
|
||||
}
|
||||
|
|
|
@ -314,14 +314,14 @@ case class DualPaymentsDb(primary: PaymentsDb, secondary: PaymentsDb) extends Pa
|
|||
primary.listPendingIncomingPayments(from, to, paginated_opt)
|
||||
}
|
||||
|
||||
override def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = {
|
||||
runAsync(secondary.listExpiredIncomingPayments(from, to))
|
||||
primary.listExpiredIncomingPayments(from, to)
|
||||
override def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = {
|
||||
runAsync(secondary.listExpiredIncomingPayments(from, to, paginated_opt))
|
||||
primary.listExpiredIncomingPayments(from, to, paginated_opt)
|
||||
}
|
||||
|
||||
override def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = {
|
||||
runAsync(secondary.listReceivedIncomingPayments(from, to))
|
||||
primary.listReceivedIncomingPayments(from, to)
|
||||
override def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = {
|
||||
runAsync(secondary.listReceivedIncomingPayments(from, to, paginated_opt))
|
||||
primary.listReceivedIncomingPayments(from, to, paginated_opt)
|
||||
}
|
||||
|
||||
override def addOutgoingPayment(outgoingPayment: OutgoingPayment): Unit = {
|
||||
|
|
|
@ -60,10 +60,10 @@ trait IncomingPaymentsDb {
|
|||
def listPendingIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment]
|
||||
|
||||
/** List all expired (not paid) incoming payments in the given time range (milli-seconds). */
|
||||
def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment]
|
||||
def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment]
|
||||
|
||||
/** List all received (paid) incoming payments in the given time range (milli-seconds). */
|
||||
def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment]
|
||||
def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment]
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -364,9 +364,9 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit
|
|||
}
|
||||
}
|
||||
|
||||
override def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming-received", DbBackends.Postgres) {
|
||||
override def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = withMetrics("payments/list-incoming-received", DbBackends.Postgres) {
|
||||
withLock { pg =>
|
||||
using(pg.prepareStatement("SELECT * FROM payments.received WHERE received_msat > 0 AND created_at > ? AND created_at < ? ORDER BY created_at")) { statement =>
|
||||
using(pg.prepareStatement(limited("SELECT * FROM payments.received WHERE received_msat > 0 AND created_at > ? AND created_at < ? ORDER BY created_at", paginated_opt))) { statement =>
|
||||
statement.setTimestamp(1, from.toSqlTimestamp)
|
||||
statement.setTimestamp(2, to.toSqlTimestamp)
|
||||
statement.executeQuery().flatMap(parseIncomingPayment).toSeq
|
||||
|
@ -385,9 +385,9 @@ class PgPaymentsDb(implicit ds: DataSource, lock: PgLock) extends PaymentsDb wit
|
|||
}
|
||||
}
|
||||
|
||||
override def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming-expired", DbBackends.Postgres) {
|
||||
override def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = withMetrics("payments/list-incoming-expired", DbBackends.Postgres) {
|
||||
withLock { pg =>
|
||||
using(pg.prepareStatement("SELECT * FROM payments.received WHERE received_msat IS NULL AND created_at > ? AND created_at < ? AND expire_at < ? ORDER BY created_at")) { statement =>
|
||||
using(pg.prepareStatement(limited("SELECT * FROM payments.received WHERE received_msat IS NULL AND created_at > ? AND created_at < ? AND expire_at < ? ORDER BY created_at", paginated_opt))) { statement =>
|
||||
statement.setTimestamp(1, from.toSqlTimestamp)
|
||||
statement.setTimestamp(2, to.toSqlTimestamp)
|
||||
statement.setTimestamp(3, Timestamp.from(Instant.now()))
|
||||
|
|
|
@ -367,8 +367,8 @@ class SqlitePaymentsDb(val sqlite: Connection) extends PaymentsDb with Logging {
|
|||
}
|
||||
}
|
||||
|
||||
override def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming-received", DbBackends.Sqlite) {
|
||||
using(sqlite.prepareStatement("SELECT * FROM received_payments WHERE received_msat > 0 AND created_at > ? AND created_at < ? ORDER BY created_at")) { statement =>
|
||||
override def listReceivedIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = withMetrics("payments/list-incoming-received", DbBackends.Sqlite) {
|
||||
using(sqlite.prepareStatement(limited("SELECT * FROM received_payments WHERE received_msat > 0 AND created_at > ? AND created_at < ? ORDER BY created_at", paginated_opt))) { statement =>
|
||||
statement.setLong(1, from.toLong)
|
||||
statement.setLong(2, to.toLong)
|
||||
statement.executeQuery().flatMap(parseIncomingPayment).toSeq
|
||||
|
@ -384,8 +384,8 @@ class SqlitePaymentsDb(val sqlite: Connection) extends PaymentsDb with Logging {
|
|||
}
|
||||
}
|
||||
|
||||
override def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli): Seq[IncomingPayment] = withMetrics("payments/list-incoming-expired", DbBackends.Sqlite) {
|
||||
using(sqlite.prepareStatement("SELECT * FROM received_payments WHERE received_msat IS NULL AND created_at > ? AND created_at < ? AND expire_at < ? ORDER BY created_at")) { statement =>
|
||||
override def listExpiredIncomingPayments(from: TimestampMilli, to: TimestampMilli, paginated_opt: Option[Paginated]): Seq[IncomingPayment] = withMetrics("payments/list-incoming-expired", DbBackends.Sqlite) {
|
||||
using(sqlite.prepareStatement(limited("SELECT * FROM received_payments WHERE received_msat IS NULL AND created_at > ? AND created_at < ? AND expire_at < ? ORDER BY created_at", paginated_opt))) { statement =>
|
||||
statement.setLong(1, from.toLong)
|
||||
statement.setLong(2, to.toLong)
|
||||
statement.setLong(3, TimestampMilli.now().toLong)
|
||||
|
|
|
@ -58,7 +58,7 @@ class InvoicePurger private(paymentsDb: IncomingPaymentsDb, context: ActorContex
|
|||
case TickPurge =>
|
||||
val now = TimestampMilli.now()
|
||||
val start = if (fullScan) 0 unixms else now - 15.days
|
||||
val expiredPayments = paymentsDb.listExpiredIncomingPayments(start, now)
|
||||
val expiredPayments = paymentsDb.listExpiredIncomingPayments(start, now, None)
|
||||
// purge expired payments
|
||||
expiredPayments.foreach(p => paymentsDb.removeIncomingPayment(p.invoice.paymentHash))
|
||||
|
||||
|
|
|
@ -205,7 +205,7 @@ class PaymentsDbSpec extends AnyFunSuite {
|
|||
|
||||
assert(db.listOutgoingPayments(1 unixms, 2000 unixms) == Seq(ps1, ps2, ps3, ps4, ps5, ps6))
|
||||
assert(db.listIncomingPayments(1 unixms, TimestampMilli.now(), None) == Seq(pr1, pr2, pr3))
|
||||
assert(db.listExpiredIncomingPayments(1 unixms, 2000 unixms) == Seq(pr2))
|
||||
assert(db.listExpiredIncomingPayments(1 unixms, 2000 unixms, None) == Seq(pr2))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -509,9 +509,9 @@ class PaymentsDbSpec extends AnyFunSuite {
|
|||
assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2100-12-31T23:59:59.00Z").toEpochMilli), None) == Seq(pr2, pr1))
|
||||
assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2020-12-31T23:59:59.00Z").toEpochMilli), None) == Seq(pr2))
|
||||
assert(db.listIncomingPayments(TimestampMilli(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli), None) == Seq.empty)
|
||||
assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2100-12-31T23:59:59.00Z").toEpochMilli)) == Seq(pr2))
|
||||
assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2020-12-31T23:59:59.00Z").toEpochMilli)) == Seq(pr2))
|
||||
assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli)) == Seq.empty)
|
||||
assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2100-12-31T23:59:59.00Z").toEpochMilli), None) == Seq(pr2))
|
||||
assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2020-12-31T23:59:59.00Z").toEpochMilli), None) == Seq(pr2))
|
||||
assert(db.listExpiredIncomingPayments(TimestampMilli(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2011-12-31T23:59:59.00Z").toEpochMilli), None) == Seq.empty)
|
||||
|
||||
assert(db.listOutgoingPayments(TimestampMilli(Instant.parse("2020-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2021-12-31T23:59:59.00Z").toEpochMilli)) == Seq(ps2, ps1, ps3))
|
||||
assert(db.listOutgoingPayments(TimestampMilli(Instant.parse("2010-01-01T00:00:00.00Z").toEpochMilli), TimestampMilli(Instant.parse("2021-01-15T23:59:59.00Z").toEpochMilli)) == Seq(ps2, ps1))
|
||||
|
@ -690,8 +690,8 @@ class PaymentsDbSpec extends AnyFunSuite {
|
|||
|
||||
val now = TimestampMilli.now()
|
||||
assert(db.listIncomingPayments(0 unixms, now, None) == Seq(expiredPayment1, expiredPayment2, pendingPayment1, pendingPayment2, payment1.copy(status = IncomingPaymentStatus.Pending), payment2.copy(status = IncomingPaymentStatus.Pending), payment3))
|
||||
assert(db.listExpiredIncomingPayments(0 unixms, now) == Seq(expiredPayment1, expiredPayment2))
|
||||
assert(db.listReceivedIncomingPayments(0 unixms, now) == Seq(payment3))
|
||||
assert(db.listExpiredIncomingPayments(0 unixms, now, None) == Seq(expiredPayment1, expiredPayment2))
|
||||
assert(db.listReceivedIncomingPayments(0 unixms, now, None) == Seq(payment3))
|
||||
assert(db.listPendingIncomingPayments(0 unixms, now, None) == Seq(pendingPayment1, pendingPayment2, payment1.copy(status = IncomingPaymentStatus.Pending), payment2.copy(status = IncomingPaymentStatus.Pending)))
|
||||
|
||||
db.receiveIncomingPayment(paidInvoice1.paymentHash, 461 msat, receivedAt1)
|
||||
|
@ -712,7 +712,7 @@ class PaymentsDbSpec extends AnyFunSuite {
|
|||
assert(db.listIncomingPayments(0 unixms, now, Some(Paginated(2, 2))) == Seq(pendingPayment1, pendingPayment2))
|
||||
assert(db.listPendingIncomingPayments(0 unixms, now, None) == Seq(pendingPayment1, pendingPayment2))
|
||||
assert(db.listPendingIncomingPayments(0 unixms, now, Some(Paginated(1, 1))) == Seq(pendingPayment2))
|
||||
assert(db.listReceivedIncomingPayments(0 unixms, now) == Seq(payment1, payment2, payment4))
|
||||
assert(db.listReceivedIncomingPayments(0 unixms, now, None) == Seq(payment1, payment2, payment4))
|
||||
|
||||
assert(db.removeIncomingPayment(paidInvoice1.paymentHash).isFailure)
|
||||
db.removeIncomingPayment(paidInvoice1.paymentHash).failed.foreach(e => assert(e.getMessage == "Cannot remove a received incoming payment"))
|
||||
|
|
|
@ -63,8 +63,8 @@ class InvoicePurgerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("ap
|
|||
assert(db.listIncomingPayments(0 unixms, now, None) == expiredPayments ++ pendingPayments ++ paidPayments)
|
||||
assert(db.listIncomingPayments(now - 100.days, now, None) == pendingPayments ++ paidPayments)
|
||||
assert(db.listPendingIncomingPayments(0 unixms, now, None) == pendingPayments)
|
||||
assert(db.listReceivedIncomingPayments(0 unixms, now) == paidPayments)
|
||||
assert(db.listExpiredIncomingPayments(0 unixms, now) == expiredPayments)
|
||||
assert(db.listReceivedIncomingPayments(0 unixms, now, None) == paidPayments)
|
||||
assert(db.listExpiredIncomingPayments(0 unixms, now, None) == expiredPayments)
|
||||
|
||||
val probe = testKit.createTestProbe[PurgeEvent]()
|
||||
system.eventStream ! EventStream.Subscribe(probe.ref)
|
||||
|
@ -74,7 +74,7 @@ class InvoicePurgerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("ap
|
|||
// check that purge runs before the default first interval of 24 hours
|
||||
probe.expectMessage(5 seconds, PurgeCompleted)
|
||||
probe.expectNoMessage()
|
||||
assert(db.listExpiredIncomingPayments(0 unixms, now).isEmpty)
|
||||
assert(db.listExpiredIncomingPayments(0 unixms, now, None).isEmpty)
|
||||
assert(db.listIncomingPayments(0 unixms, now, None) == pendingPayments ++ paidPayments)
|
||||
|
||||
testKit.stop(purger)
|
||||
|
@ -105,7 +105,7 @@ class InvoicePurgerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("ap
|
|||
// check that the initial purge scanned the entire database
|
||||
probe.expectMessage(10 seconds, PurgeCompleted)
|
||||
probe.expectNoMessage()
|
||||
assert(db.listExpiredIncomingPayments(0 unixms, TimestampMilli.now()).isEmpty)
|
||||
assert(db.listExpiredIncomingPayments(0 unixms, TimestampMilli.now(), None).isEmpty)
|
||||
|
||||
// add an expired invoice from before the 15 days look back period
|
||||
val expiredInvoice3 = Bolt11Invoice(Block.TestnetGenesisBlock.hash, Some(100 msat), randomBytes32(), alicePriv, Left("expired invoice3"), CltvExpiryDelta(18),
|
||||
|
@ -122,7 +122,7 @@ class InvoicePurgerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("ap
|
|||
// check that subsequent purge runs do not go back > 15 days
|
||||
probe.expectMessage(10 seconds, PurgeCompleted)
|
||||
probe.expectNoMessage()
|
||||
assert(db.listExpiredIncomingPayments(0 unixms, TimestampMilli.now()) == Seq(expiredPayment3))
|
||||
assert(db.listExpiredIncomingPayments(0 unixms, TimestampMilli.now(), None) == Seq(expiredPayment3))
|
||||
|
||||
testKit.stop(purger)
|
||||
}
|
||||
|
|
|
@ -95,6 +95,14 @@ trait Payment {
|
|||
}
|
||||
}
|
||||
|
||||
val listReceivedPayments: Route = postRequest("listreceivedpayments") { implicit t =>
|
||||
withPaginated { paginated_opt =>
|
||||
formFields(fromFormParam(), toFormParam()) { (from, to) =>
|
||||
complete(eclairApi.receivedPayments(from, to, paginated_opt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val payOffer: Route = postRequest("payoffer") { implicit t =>
|
||||
formFields(offerFormParam, amountMsatFormParam, "quantity".as[Long].?, "maxAttempts".as[Int].?, "maxFeeFlatSat".as[Satoshi].?, "maxFeePct".as[Double].?, "externalId".?, "pathFindingExperimentName".?, "blocking".as[Boolean].?) {
|
||||
case (offer, amountMsat, quantity_opt, maxAttempts_opt, maxFeeFlat_opt, maxFeePct_opt, externalId_opt, pathFindingExperimentName_opt, blocking_opt) =>
|
||||
|
@ -105,6 +113,6 @@ trait Payment {
|
|||
}
|
||||
}
|
||||
|
||||
val paymentRoutes: Route = usableBalances ~ payInvoice ~ sendToNode ~ sendToRoute ~ getSentInfo ~ getReceivedInfo ~ payOffer
|
||||
val paymentRoutes: Route = usableBalances ~ payInvoice ~ sendToNode ~ sendToRoute ~ getSentInfo ~ getReceivedInfo ~ listReceivedPayments ~ payOffer
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue