offers: make 'used' flag more useful.

We used to only set it for single-use offers (where it's required),
but it's still interesting for multi-use offers, so let's keep it
there.

We also put this field in the documentation.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2021-02-18 14:45:54 +10:30
parent 960ef95b23
commit 874ca99c32
12 changed files with 117 additions and 77 deletions

View File

@@ -29,9 +29,29 @@ set and is true, only offers with \fBactive\fR true are returned\.
.fi .fi
.SH RETURN VALUE .SH RETURN VALUE
On success, an array \fIoffers\fR of objects is returned\. Each object contains On success, an array \fIoffers\fR of objects is returned\. Each object contains:
\fIoffer_id\fR, \fIactive\fR, \fIsingle_use\fR, \fIbolt12\fR and \fIused\fR\.
.RS
.IP \[bu]
\fIoffer_id\fR: the hash of the offer\.
.IP \[bu]
\fIactive\fR: true
.IP \[bu]
\fIsingle_use\fR: true if \fIsingle_use\fR was specified\.
.IP \[bu]
\fIbolt12\fR: the bolt12 offer, starting with "lno1"
.IP \[bu]
\fIused\fR: true if an associated invoice has been paid\.
.RE
Optionally:
.RS
.IP \[bu]
\fIlabel\fR: the user-specified label\.
.RE
.SH EXAMPLE JSON RESPONSE .SH EXAMPLE JSON RESPONSE
.nf .nf
.RS .RS
@@ -49,7 +69,7 @@ On success, an array \fIoffers\fR of objects is returned\. Each object contains
"active": true, "active": true,
"single_use": false, "single_use": false,
"bolt12": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcxqd24x3qgqgqlgzs3gdhkven9v5sxvmmjype82um50ys3ug9kxsmqdvj3c6ut2cuu2s4nrf8k2dulccgaqcdzxgp583utjlu49rcyqt8hc3s797umxn3r9367rdqc577rma7key58fywkajxnuzyapge86hj2pg80rjrma40xdqrxnsnva5l3ce7hz4ua8wf755dees4y9vnq", "bolt12": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcxqd24x3qgqgqlgzs3gdhkven9v5sxvmmjype82um50ys3ug9kxsmqdvj3c6ut2cuu2s4nrf8k2dulccgaqcdzxgp583utjlu49rcyqt8hc3s797umxn3r9367rdqc577rma7key58fywkajxnuzyapge86hj2pg80rjrma40xdqrxnsnva5l3ce7hz4ua8wf755dees4y9vnq",
"used": false "used": true
} }
] ]
} }
@@ -68,4 +88,4 @@ Rusty Russell \fI<rusty@rustcorp.com.au\fR> is mainly responsible\.
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
\" SHA256STAMP:f654c090c24a1cae9a3947b4ba0bfd051949502a5de6d78d899a7f6cf73a8fe6 \" SHA256STAMP:bc4abe90e5475a272c61af45e4f00be9f3fab89d3adca767b577b46d70d33726

View File

@@ -29,8 +29,17 @@ EXAMPLE JSON REQUEST
RETURN VALUE RETURN VALUE
------------ ------------
On success, an array *offers* of objects is returned. Each object contains On success, an array *offers* of objects is returned. Each object contains:
*offer_id*, *active*, *single_use*, *bolt12* and *used*.
* *offer_id*: the hash of the offer.
* *active*: true
* *single_use*: true if *single_use* was specified.
* *bolt12*: the bolt12 offer, starting with "lno1"
* *used*: true if an associated invoice has been paid.
Optionally:
* *label*: the user-specified label.
EXAMPLE JSON RESPONSE EXAMPLE JSON RESPONSE
----- -----
@@ -49,7 +58,7 @@ EXAMPLE JSON RESPONSE
"active": true, "active": true,
"single_use": false, "single_use": false,
"bolt12": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcxqd24x3qgqgqlgzs3gdhkven9v5sxvmmjype82um50ys3ug9kxsmqdvj3c6ut2cuu2s4nrf8k2dulccgaqcdzxgp583utjlu49rcyqt8hc3s797umxn3r9367rdqc577rma7key58fywkajxnuzyapge86hj2pg80rjrma40xdqrxnsnva5l3ce7hz4ua8wf755dees4y9vnq", "bolt12": "lno1qgsqvgnwgcg35z6ee2h3yczraddm72xrfua9uve2rlrm9deu7xyfzrcxqd24x3qgqgqlgzs3gdhkven9v5sxvmmjype82um50ys3ug9kxsmqdvj3c6ut2cuu2s4nrf8k2dulccgaqcdzxgp583utjlu49rcyqt8hc3s797umxn3r9367rdqc577rma7key58fywkajxnuzyapge86hj2pg80rjrma40xdqrxnsnva5l3ce7hz4ua8wf755dees4y9vnq",
"used": false "used": true
} }
] ]
} }

23
doc/lightning-offer.7 generated
View File

@@ -100,27 +100,8 @@ invoices will be expired (i\.e\. only one person can pay this offer)\.
.SH RETURN VALUE .SH RETURN VALUE
On success, an object as follows is returned: On success, an object as follows is returned as per \fBlightning-listoffers\fR(7)\.
.RS
.IP \[bu]
\fIoffer_id\fR: the hash of the offer\.
.IP \[bu]
\fIactive\fR: true
.IP \[bu]
\fIsingle_use\fR: true if \fIsingle_use\fR was specified\.
.IP \[bu]
\fIbolt12\fR: the bolt12 offer, starting with "lno1"
.RE
Optionally:
.RS
.IP \[bu]
\fIlabel\fR: the user-specified label\.
.RE
On failure, an error is returned and no offer is created\. If the On failure, an error is returned and no offer is created\. If the
lightning process fails before responding, the caller should use lightning process fails before responding, the caller should use
@@ -149,4 +130,4 @@ Rusty Russell \fI<rusty@rustcorp.com.au\fR> is mainly responsible\.
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
\" SHA256STAMP:4ab9701b01ad474482f7aa8d31ea2c1170904402c2f222f6d5a9ba826e963457 \" SHA256STAMP:32c3f313b94ca968683060c9b8ba81c76b610b3b0f0c604a24a2f88bb8130c2c

View File

@@ -89,15 +89,7 @@ invoices will be expired (i.e. only one person can pay this offer).
RETURN VALUE RETURN VALUE
------------ ------------
On success, an object as follows is returned: On success, an object as follows is returned as per lightning-listoffers(7).
* *offer_id*: the hash of the offer.
* *active*: true
* *single_use*: true if *single_use* was specified.
* *bolt12*: the bolt12 offer, starting with "lno1"
Optionally:
* *label*: the user-specified label.
On failure, an error is returned and no offer is created. If the On failure, an error is returned and no offer is created. If the
lightning process fails before responding, the caller should use lightning process fails before responding, the caller should use

View File

@@ -44,6 +44,13 @@ static void update_feerates(struct lightningd *ld, struct channel *channel)
if (!feerate) if (!feerate)
return; return;
log_debug(ld->log,
"update_feerates: feerate = %u, min=%u, max=%u, penalty=%u",
feerate,
feerate_min(ld, NULL),
feerate_max(ld, NULL),
try_get_feerate(ld->topology, FEERATE_PENALTY));
msg = towire_channeld_feerates(NULL, feerate, msg = towire_channeld_feerates(NULL, feerate,
feerate_min(ld, NULL), feerate_min(ld, NULL),
feerate_max(ld, NULL), feerate_max(ld, NULL),

View File

@@ -21,7 +21,7 @@ static void json_populate_offer(struct json_stream *response,
json_add_bool(response, "active", offer_status_active(status)); json_add_bool(response, "active", offer_status_active(status));
json_add_bool(response, "single_use", offer_status_single(status)); json_add_bool(response, "single_use", offer_status_single(status));
json_add_string(response, "bolt12", b12); json_add_string(response, "bolt12", b12);
json_add_bool(response, "used", status == OFFER_USED); json_add_bool(response, "used", offer_status_used(status));
if (label) if (label)
json_add_escaped_string(response, "label", label); json_add_escaped_string(response, "label", label);
} }
@@ -96,9 +96,9 @@ static struct command_result *json_createoffer(struct command *cmd,
return command_param_failed(); return command_param_failed();
if (*single_use) if (*single_use)
status = OFFER_SINGLE_USE; status = OFFER_SINGLE_USE_UNUSED;
else else
status = OFFER_MULTIPLE_USE; status = OFFER_MULTIPLE_USE_UNUSED;
merkle_tlv(offer->fields, &merkle); merkle_tlv(offer->fields, &merkle);
offer->signature = tal(offer, struct bip340sig); offer->signature = tal(offer, struct bip340sig);
if (!pubkey32_from_node_id(&key, &cmd->ld->id)) if (!pubkey32_from_node_id(&key, &cmd->ld->id))

View File

@@ -3915,29 +3915,32 @@ def test_fetchinvoice(node_factory, bitcoind):
# Simple offer first. # Simple offer first.
offer1 = l3.rpc.call('offer', {'amount': '2msat', offer1 = l3.rpc.call('offer', {'amount': '2msat',
'description': 'simple test'})['bolt12'] 'description': 'simple test'})
inv1 = l1.rpc.call('fetchinvoice', {'offer': offer1}) inv1 = l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12']})
inv2 = l1.rpc.call('fetchinvoice', {'offer': offer1}) inv2 = l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12']})
assert inv1 != inv2 assert inv1 != inv2
assert 'next_period' not in inv1 assert 'next_period' not in inv1
assert 'next_period' not in inv2 assert 'next_period' not in inv2
assert only_one(l3.rpc.call('listoffers', [offer1['offer_id']])['offers'])['used'] is False
l1.rpc.pay(inv1['invoice']) l1.rpc.pay(inv1['invoice'])
assert only_one(l3.rpc.call('listoffers', [offer1['offer_id']])['offers'])['used'] is True
l1.rpc.pay(inv2['invoice']) l1.rpc.pay(inv2['invoice'])
assert only_one(l3.rpc.call('listoffers', [offer1['offer_id']])['offers'])['used'] is True
# We can also set the amount explicitly, to tip. # We can also set the amount explicitly, to tip.
inv1 = l1.rpc.call('fetchinvoice', {'offer': offer1, 'msatoshi': 3}) inv1 = l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12'], 'msatoshi': 3})
assert l1.rpc.call('decode', [inv1['invoice']])['amount_msat'] == 3 assert l1.rpc.call('decode', [inv1['invoice']])['amount_msat'] == 3
l1.rpc.pay(inv1['invoice']) l1.rpc.pay(inv1['invoice'])
# More than ~5x expected is rejected as absurd (it's actually a divide test, # More than ~5x expected is rejected as absurd (it's actually a divide test,
# which means we need 15 here, not 11). # which means we need 15 here, not 11).
with pytest.raises(RpcError, match="Remote node sent failure message.*Amount vastly exceeds 2msat"): with pytest.raises(RpcError, match="Remote node sent failure message.*Amount vastly exceeds 2msat"):
l1.rpc.call('fetchinvoice', {'offer': offer1, 'msatoshi': 15}) l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12'], 'msatoshi': 15})
# Underpay is rejected. # Underpay is rejected.
with pytest.raises(RpcError, match="Remote node sent failure message.*Amount must be at least 2msat"): with pytest.raises(RpcError, match="Remote node sent failure message.*Amount must be at least 2msat"):
l1.rpc.call('fetchinvoice', {'offer': offer1, 'msatoshi': 1}) l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12'], 'msatoshi': 1})
# Single-use invoice can be fetched multiple times, only paid once. # Single-use invoice can be fetched multiple times, only paid once.
offer2 = l3.rpc.call('offer', {'amount': '1msat', offer2 = l3.rpc.call('offer', {'amount': '1msat',
@@ -3963,9 +3966,10 @@ def test_fetchinvoice(node_factory, bitcoind):
# Recurring offer. # Recurring offer.
offer3 = l2.rpc.call('offer', {'amount': '1msat', offer3 = l2.rpc.call('offer', {'amount': '1msat',
'description': 'recurring test', 'description': 'recurring test',
'recurrence': '1minutes'})['bolt12'] 'recurrence': '1minutes'})
assert only_one(l2.rpc.call('listoffers', [offer3['offer_id']])['offers'])['used'] is False
ret = l1.rpc.call('fetchinvoice', {'offer': offer3, ret = l1.rpc.call('fetchinvoice', {'offer': offer3['bolt12'],
'recurrence_counter': 0, 'recurrence_counter': 0,
'recurrence_label': 'test recurrence'}) 'recurrence_label': 'test recurrence'})
period1 = ret['next_period'] period1 = ret['next_period']
@@ -3973,10 +3977,12 @@ def test_fetchinvoice(node_factory, bitcoind):
assert period1['endtime'] == period1['starttime'] + 59 assert period1['endtime'] == period1['starttime'] + 59
assert period1['paywindow_start'] == period1['starttime'] - 60 assert period1['paywindow_start'] == period1['starttime'] - 60
assert period1['paywindow_end'] == period1['endtime'] assert period1['paywindow_end'] == period1['endtime']
assert only_one(l2.rpc.call('listoffers', [offer3['offer_id']])['offers'])['used'] is False
l1.rpc.pay(ret['invoice'], label='test recurrence') l1.rpc.pay(ret['invoice'], label='test recurrence')
assert only_one(l2.rpc.call('listoffers', [offer3['offer_id']])['offers'])['used'] is True
ret = l1.rpc.call('fetchinvoice', {'offer': offer3, ret = l1.rpc.call('fetchinvoice', {'offer': offer3['bolt12'],
'recurrence_counter': 1, 'recurrence_counter': 1,
'recurrence_label': 'test recurrence'}) 'recurrence_label': 'test recurrence'})
period2 = ret['next_period'] period2 = ret['next_period']
@@ -3988,7 +3994,7 @@ def test_fetchinvoice(node_factory, bitcoind):
# Can't request 2 before paying 1. # Can't request 2 before paying 1.
with pytest.raises(RpcError, match='previous invoice has not been paid'): with pytest.raises(RpcError, match='previous invoice has not been paid'):
l1.rpc.call('fetchinvoice', {'offer': offer3, l1.rpc.call('fetchinvoice', {'offer': offer3['bolt12'],
'recurrence_counter': 2, 'recurrence_counter': 2,
'recurrence_label': 'test recurrence'}) 'recurrence_label': 'test recurrence'})
@@ -3996,7 +4002,7 @@ def test_fetchinvoice(node_factory, bitcoind):
# Now we can, but it's too early: # Now we can, but it's too early:
with pytest.raises(RpcError, match="Too early: can't send until time {}".format(period1['starttime'])): with pytest.raises(RpcError, match="Too early: can't send until time {}".format(period1['starttime'])):
l1.rpc.call('fetchinvoice', {'offer': offer3, l1.rpc.call('fetchinvoice', {'offer': offer3['bolt12'],
'recurrence_counter': 2, 'recurrence_counter': 2,
'recurrence_label': 'test recurrence'}) 'recurrence_label': 'test recurrence'})
@@ -4004,14 +4010,14 @@ def test_fetchinvoice(node_factory, bitcoind):
while time.time() < period1['starttime']: while time.time() < period1['starttime']:
time.sleep(1) time.sleep(1)
l1.rpc.call('fetchinvoice', {'offer': offer3, l1.rpc.call('fetchinvoice', {'offer': offer3['bolt12'],
'recurrence_counter': 2, 'recurrence_counter': 2,
'recurrence_label': 'test recurrence'}) 'recurrence_label': 'test recurrence'})
# Check we can request invoice without a channel. # Check we can request invoice without a channel.
l4 = node_factory.get_node(options={'experimental-offers': None}) l4 = node_factory.get_node(options={'experimental-offers': None})
l4.rpc.connect(l2.info['id'], 'localhost', l2.port) l4.rpc.connect(l2.info['id'], 'localhost', l2.port)
ret = l4.rpc.call('fetchinvoice', {'offer': offer3, ret = l4.rpc.call('fetchinvoice', {'offer': offer3['bolt12'],
'recurrence_counter': 0, 'recurrence_counter': 0,
'recurrence_label': 'test nochannel'}) 'recurrence_label': 'test nochannel'})
@@ -4037,7 +4043,7 @@ def test_fetchinvoice(node_factory, bitcoind):
# Test timeout. # Test timeout.
l3.stop() l3.stop()
with pytest.raises(RpcError, match='Timeout waiting for response'): with pytest.raises(RpcError, match='Timeout waiting for response'):
l1.rpc.call('fetchinvoice', {'offer': offer1, 'timeout': 10}) l1.rpc.call('fetchinvoice', {'offer': offer1['bolt12'], 'timeout': 10})
# Now try an offer with a more complex paywindow (only 10 seconds before) # Now try an offer with a more complex paywindow (only 10 seconds before)
offer = l2.rpc.call('offer', {'amount': '1msat', offer = l2.rpc.call('offer', {'amount': '1msat',
@@ -4089,15 +4095,17 @@ def test_sendinvoice(node_factory, bitcoind):
# Simple offer to send money (balances channel a little) # Simple offer to send money (balances channel a little)
offer = l1.rpc.call('offerout', {'amount': '100000sat', offer = l1.rpc.call('offerout', {'amount': '100000sat',
'description': 'simple test'})['bolt12'] 'description': 'simple test'})
print(offer)
# Fetchinvoice will refuse, since you're supposed to send an invoice. # Fetchinvoice will refuse, since you're supposed to send an invoice.
with pytest.raises(RpcError, match='Offer wants an invoice, not invoice_request'): with pytest.raises(RpcError, match='Offer wants an invoice, not invoice_request'):
l2.rpc.call('fetchinvoice', {'offer': offer}) l2.rpc.call('fetchinvoice', {'offer': offer['bolt12']})
# used will be false
assert only_one(l1.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is False
# sendinvoice should work. # sendinvoice should work.
out = l2.rpc.call('sendinvoice', {'offer': offer, out = l2.rpc.call('sendinvoice', {'offer': offer['bolt12'],
'label': 'test sendinvoice 1'}) 'label': 'test sendinvoice 1'})
print(out) print(out)
assert out['label'] == 'test sendinvoice 1' assert out['label'] == 'test sendinvoice 1'
@@ -4117,22 +4125,29 @@ def test_sendinvoice(node_factory, bitcoind):
# *but* if it hasn't heard about payment success yet, l2 will fail # *but* if it hasn't heard about payment success yet, l2 will fail
# simply because payments are already pending. # simply because payments are already pending.
with pytest.raises(RpcError, match='Offer no longer available|pay attempt failed'): with pytest.raises(RpcError, match='Offer no longer available|pay attempt failed'):
l2.rpc.call('sendinvoice', {'offer': offer, l2.rpc.call('sendinvoice', {'offer': offer['bolt12'],
'label': 'test sendinvoice 2'}) 'label': 'test sendinvoice 2'})
# Technically, l1 may not have gotten payment success, so we need to wait.
wait_for(lambda: only_one(l1.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is True)
# Now try a refund. # Now try a refund.
offer = l2.rpc.call('offer', {'amount': '100msat', offer = l2.rpc.call('offer', {'amount': '100msat',
'description': 'simple test'})['bolt12'] 'description': 'simple test'})
assert only_one(l2.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is False
inv = l1.rpc.call('fetchinvoice', {'offer': offer}) inv = l1.rpc.call('fetchinvoice', {'offer': offer['bolt12']})
l1.rpc.pay(inv['invoice']) l1.rpc.pay(inv['invoice'])
assert only_one(l2.rpc.call('listoffers', [offer['offer_id']])['offers'])['used'] is True
refund = l2.rpc.call('offerout', {'amount': '100msat', refund = l2.rpc.call('offerout', {'amount': '100msat',
'description': 'refund test', 'description': 'refund test',
'refund_for': inv['invoice']})['bolt12'] 'refund_for': inv['invoice']})
assert only_one(l2.rpc.call('listoffers', [refund['offer_id']])['offers'])['used'] is False
l1.rpc.call('sendinvoice', {'offer': refund, l1.rpc.call('sendinvoice', {'offer': refund['bolt12'],
'label': 'test sendinvoice refund'}) 'label': 'test sendinvoice refund'})
wait_for(lambda: only_one(l2.rpc.call('listoffers', [refund['offer_id']])['offers'])['used'] is True)
def test_self_pay(node_factory): def test_self_pay(node_factory):

View File

@@ -1792,4 +1792,4 @@ struct db_query db_postgres_queries[] = {
#endif /* LIGHTNINGD_WALLET_GEN_DB_POSTGRES */ #endif /* LIGHTNINGD_WALLET_GEN_DB_POSTGRES */
// SHA256STAMP:12a32ce7169fd1200edf6a6361c65fa6bd94ff9cfee926bd0eb6a882f46aa917 // SHA256STAMP:d5554774287ded802c96fc975565e5557e5f7e174fea04ad7ec080400a15992b

View File

@@ -1792,4 +1792,4 @@ struct db_query db_sqlite3_queries[] = {
#endif /* LIGHTNINGD_WALLET_GEN_DB_SQLITE3 */ #endif /* LIGHTNINGD_WALLET_GEN_DB_SQLITE3 */
// SHA256STAMP:12a32ce7169fd1200edf6a6361c65fa6bd94ff9cfee926bd0eb6a882f46aa917 // SHA256STAMP:d5554774287ded802c96fc975565e5557e5f7e174fea04ad7ec080400a15992b

View File

@@ -1181,4 +1181,4 @@ msgstr ""
#: wallet/test/run-wallet.c:1395 #: wallet/test/run-wallet.c:1395
msgid "INSERT INTO channels (id) VALUES (1);" msgid "INSERT INTO channels (id) VALUES (1);"
msgstr "" msgstr ""
# SHA256STAMP:ecf013adbde1360d2544fc6a84105d620a2ae3a5acc6394288ee24e6110548f7 # SHA256STAMP:6aef0052df3b7141edbb7af83a1242a31af18b55aed8e14ca5ede7d783f86309

View File

@@ -4234,6 +4234,13 @@ void wallet_offer_mark_used(struct db *db, const struct sha256 *offer_id)
type_to_string(tmpctx, struct sha256, offer_id), type_to_string(tmpctx, struct sha256, offer_id),
status); status);
if (status == OFFER_SINGLE_USE) if (!offer_status_used(status)) {
offer_status_update(db, offer_id, status, OFFER_USED); enum offer_status newstatus;
if (offer_status_single(status))
newstatus = OFFER_SINGLE_USE_USED;
else
newstatus = OFFER_MULTIPLE_USE_USED;
offer_status_update(db, offer_id, status, newstatus);
}
} }

View File

@@ -1354,9 +1354,10 @@ void wallet_penalty_base_delete(struct wallet *w, u64 chan_id, u64 commitnum);
#define OFFER_STATUS_SINGLE_F 0x2 #define OFFER_STATUS_SINGLE_F 0x2
#define OFFER_STATUS_USED_F 0x4 #define OFFER_STATUS_USED_F 0x4
enum offer_status { enum offer_status {
OFFER_MULTIPLE_USE = OFFER_STATUS_ACTIVE_F, OFFER_MULTIPLE_USE_UNUSED = OFFER_STATUS_ACTIVE_F,
OFFER_SINGLE_USE = OFFER_STATUS_ACTIVE_F|OFFER_STATUS_SINGLE_F, OFFER_MULTIPLE_USE_USED = OFFER_STATUS_ACTIVE_F|OFFER_STATUS_USED_F,
OFFER_USED = OFFER_STATUS_SINGLE_F|OFFER_STATUS_USED_F, OFFER_SINGLE_USE_UNUSED = OFFER_STATUS_ACTIVE_F|OFFER_STATUS_SINGLE_F,
OFFER_SINGLE_USE_USED = OFFER_STATUS_SINGLE_F|OFFER_STATUS_USED_F,
OFFER_SINGLE_DISABLED = OFFER_STATUS_SINGLE_F, OFFER_SINGLE_DISABLED = OFFER_STATUS_SINGLE_F,
OFFER_MULTIPLE_DISABLED = 0, OFFER_MULTIPLE_DISABLED = 0,
}; };
@@ -1364,14 +1365,17 @@ enum offer_status {
static inline enum offer_status offer_status_in_db(enum offer_status s) static inline enum offer_status offer_status_in_db(enum offer_status s)
{ {
switch (s) { switch (s) {
case OFFER_MULTIPLE_USE: case OFFER_MULTIPLE_USE_UNUSED:
BUILD_ASSERT(OFFER_MULTIPLE_USE == 1); BUILD_ASSERT(OFFER_MULTIPLE_USE_UNUSED == 1);
return s; return s;
case OFFER_SINGLE_USE: case OFFER_MULTIPLE_USE_USED:
BUILD_ASSERT(OFFER_SINGLE_USE == 3); BUILD_ASSERT(OFFER_MULTIPLE_USE_USED == 5);
return s; return s;
case OFFER_USED: case OFFER_SINGLE_USE_UNUSED:
BUILD_ASSERT(OFFER_USED == 6); BUILD_ASSERT(OFFER_SINGLE_USE_UNUSED == 3);
return s;
case OFFER_SINGLE_USE_USED:
BUILD_ASSERT(OFFER_SINGLE_USE_USED == 6);
return s; return s;
case OFFER_SINGLE_DISABLED: case OFFER_SINGLE_DISABLED:
BUILD_ASSERT(OFFER_SINGLE_DISABLED == 2); BUILD_ASSERT(OFFER_SINGLE_DISABLED == 2);
@@ -1393,6 +1397,11 @@ static inline bool offer_status_single(enum offer_status s)
return s & OFFER_STATUS_SINGLE_F; return s & OFFER_STATUS_SINGLE_F;
} }
static inline bool offer_status_used(enum offer_status s)
{
return s & OFFER_STATUS_USED_F;
}
/** /**
* Store an offer in the database. * Store an offer in the database.
* @w: the wallet * @w: the wallet