Skip to content

Commit e3719b3

Browse files
committed
offers: add used_count to listoffers
This adds a new 'used_count' field to the 'listoffers' RPC command output to see the exact number of times a BOLT12 offer has been paid by counting associated settled invoices. Changelog-Added: add a new 'used_count' field to the 'listoffers' RPC command Closes #9146
1 parent 2b8684f commit e3719b3

12 files changed

Lines changed: 912 additions & 805 deletions

File tree

.msggen.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3181,7 +3181,8 @@
31813181
"ListOffers.offers[].label": 6,
31823182
"ListOffers.offers[].offer_id": 1,
31833183
"ListOffers.offers[].single_use": 3,
3184-
"ListOffers.offers[].used": 5
3184+
"ListOffers.offers[].used": 5,
3185+
"ListOffers.offers[].used_count": 9
31853186
},
31863187
"ListoffersRequest": {
31873188
"ListOffers.active_only": 2,
@@ -11483,6 +11484,10 @@
1148311484
"added": "pre-v0.10.1",
1148411485
"deprecated": null
1148511486
},
11487+
"ListOffers.offers[].used_count": {
11488+
"added": "pre-v0.10.1",
11489+
"deprecated": null
11490+
},
1148611491
"ListPays": {
1148711492
"added": "pre-v0.10.1",
1148811493
"deprecated": null

cln-grpc/proto/node.proto

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cln-grpc/src/convert.rs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cln-rpc/src/model.rs

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contrib/msggen/msggen/schema.json

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24461,6 +24461,12 @@
2446124461
"True if an associated invoice has been paid."
2446224462
]
2446324463
},
24464+
"used_count": {
24465+
"type": "u64",
24466+
"description": [
24467+
"The number of times this offer has been paid."
24468+
]
24469+
},
2446424470
"label": {
2446524471
"type": "string",
2446624472
"description": [
@@ -24499,23 +24505,26 @@
2449924505
"single_use": false,
2450024506
"bolt12": "lno1qgsq000bolt210002100021000210002100021000210002100021000210002100021000210002100021000210002100021000210002100021000210002100021000",
2450124507
"description": "Fish sale!",
24502-
"used": false
24508+
"used": false,
24509+
"used_count": 1
2450324510
},
2450424511
{
2450524512
"offer_id": "offeridl22000002200000220000022000002200000220000022000002200000",
2450624513
"active": true,
2450724514
"single_use": false,
2450824515
"bolt12": "lno1qgsq000bolt220002200022000220002200022000220002200022000220002200022000220002200022000220002200022000220002200022000220002200022000",
2450924516
"description": "Coffee",
24510-
"used": false
24517+
"used": false,
24518+
"used_count": 1
2451124519
},
2451224520
{
2451324521
"offer_id": "offeridl23000002300000230000023000002300000230000023000002300000",
2451424522
"active": true,
2451524523
"single_use": false,
2451624524
"bolt12": "lno1qgsq000bolt230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000",
2451724525
"description": "Movie ticket",
24518-
"used": false
24526+
"used": false,
24527+
"used_count": 1
2451924528
}
2452024529
]
2452124530
}
@@ -24535,7 +24544,8 @@
2453524544
"active": true,
2453624545
"single_use": false,
2453724546
"bolt12": "lno1qgsq000bolt230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000",
24538-
"used": false
24547+
"used": false,
24548+
"used_count": 1
2453924549
}
2454024550
]
2454124551
}

contrib/pyln-grpc-proto/pyln/grpc/node_pb2.py

Lines changed: 796 additions & 796 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contrib/pyln-testing/pyln/testing/grpc2py.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1698,6 +1698,7 @@ def listoffers_offers2py(m):
16981698
"offer_id": hexlify(m.offer_id), # PrimitiveField in generate_composite
16991699
"single_use": m.single_use, # PrimitiveField in generate_composite
17001700
"used": m.used, # PrimitiveField in generate_composite
1701+
"used_count": m.used_count, # PrimitiveField in generate_composite
17011702
})
17021703

17031704

doc/schemas/listoffers.json

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@
9191
"True if an associated invoice has been paid."
9292
]
9393
},
94+
"used_count": {
95+
"type": "u64",
96+
"description": [
97+
"The number of times this offer has been paid."
98+
]
99+
},
94100
"label": {
95101
"type": "string",
96102
"description": [
@@ -129,23 +135,26 @@
129135
"single_use": false,
130136
"bolt12": "lno1qgsq000bolt210002100021000210002100021000210002100021000210002100021000210002100021000210002100021000210002100021000210002100021000",
131137
"description": "Fish sale!",
132-
"used": false
138+
"used": false,
139+
"used_count": 1
133140
},
134141
{
135142
"offer_id": "offeridl22000002200000220000022000002200000220000022000002200000",
136143
"active": true,
137144
"single_use": false,
138145
"bolt12": "lno1qgsq000bolt220002200022000220002200022000220002200022000220002200022000220002200022000220002200022000220002200022000220002200022000",
139146
"description": "Coffee",
140-
"used": false
147+
"used": false,
148+
"used_count": 1
141149
},
142150
{
143151
"offer_id": "offeridl23000002300000230000023000002300000230000023000002300000",
144152
"active": true,
145153
"single_use": false,
146154
"bolt12": "lno1qgsq000bolt230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000",
147155
"description": "Movie ticket",
148-
"used": false
156+
"used": false,
157+
"used_count": 1
149158
}
150159
]
151160
}
@@ -165,7 +174,8 @@
165174
"active": true,
166175
"single_use": false,
167176
"bolt12": "lno1qgsq000bolt230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000230002300023000",
168-
"used": false
177+
"used": false,
178+
"used_count": 1
169179
}
170180
]
171181
}

lightningd/offer.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ static struct command_result *json_listoffers(struct command *cmd,
179179
json_populate_offer(response,
180180
offer_id, b12,
181181
label, status, force_paths);
182+
u64 used_count = wallet_offer_count_payments(wallet, offer_id);
183+
json_add_u64(response, "used_count", used_count);
182184
description = offer_description_from_b12(tmpctx, cmd->ld, b12);
183185
if (description)
184186
json_add_stringn(response, "description", description, tal_bytelen(description));
@@ -198,6 +200,8 @@ static struct command_result *json_listoffers(struct command *cmd,
198200
json_populate_offer(response,
199201
&id, b12,
200202
label, status, force_paths);
203+
u64 used_count = wallet_offer_count_payments(wallet, offer_id);
204+
json_add_u64(response, "used_count", used_count);
201205
description = offer_description_from_b12(tmpctx, cmd->ld, b12);
202206
if (description)
203207
json_add_stringn(response, "description", description, tal_bytelen(description));

tests/test_pay.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7329,3 +7329,40 @@ def test_createproof_include(node_factory, bitcoind):
73297329
# Unknown name is an error.
73307330
with pytest.raises(RpcError, match=r'Unknown field name'):
73317331
l1.rpc.call('createproof', {'invstring': inv, 'include': ['no_such_field']})
7332+
7333+
def test_offer_used_count(node_factory, bitcoind):
7334+
"""Verify that the used_count counter increments correctly for BOLT12 offers"""
7335+
# Build a working 3-node graph with localhost permissions exactly like the working test
7336+
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True,
7337+
opts=[{},
7338+
{'dev-allow-localhost': None},
7339+
{'dev-allow-localhost': None}])
7340+
7341+
# Create a BOLT12 offer on the recipient node (l3)
7342+
offer = l3.rpc.offer('5000msat', 'used_count test')
7343+
offer_id = offer['offer_id']
7344+
7345+
# Verify that the initial usage counter on l3 is equal to 0
7346+
offers = l3.rpc.listoffers(offer_id=offer_id)['offers']
7347+
assert len(offers) == 1
7348+
assert 'used_count' in offers[0], "Error: used_count field is missing from listoffers response!"
7349+
assert offers[0]['used_count'] == 0
7350+
7351+
# The sender node (l1) requests an invoice based on l3's offer
7352+
inv = l1.rpc.fetchinvoice(offer['bolt12'])['invoice']
7353+
7354+
# Pay the invoice from l1 using xpay
7355+
l1.rpc.xpay(inv)
7356+
7357+
# Verify that l3's database counter increments to 1 after successful settlement
7358+
offers = l3.rpc.listoffers(offer_id=offer_id)['offers']
7359+
assert offers[0]['used_count'] == 1
7360+
7361+
# Perform a second payment to ensure the aggregate COUNT(*) functions properly
7362+
inv2 = l1.rpc.fetchinvoice(offer['bolt12'])['invoice']
7363+
l1.rpc.xpay(inv2)
7364+
7365+
# Verify that the counter scales up to 2
7366+
offers = l3.rpc.listoffers(offer_id=offer_id)['offers']
7367+
assert offers[0]['used_count'] == 2
7368+

0 commit comments

Comments
 (0)