taproot-assets
taproot-assets copied to clipboard
[bug]: direct payments of an asset invoice uses remote peer's quote
I have the following network configuration:
TA
edward <----> frank
Frank creates an invoice for 36000 asset and gives it to Edward. Edward pays the invoice through a direct channel. Frank gets 37088 assets.
=======================================================================================
edward sending Asset1 via frank and frank receiving 36000 Asset1_base (360.0 Asset1_friendly) via edward
MaxAllowableFee: 11
accepted_buy_quote {
peer: "027c9b0b306d37e2df89f460460a4df1073af94d088f023053f0106d0d95f3c21c"
id: "\177\316\304\215\204b\340p \r\332\300\350\350\336\3260f\336.\010\344w7\364\337U\027\212\260l\276"
scid: 17644915424660319422
asset_max_amount: 36000
ask_asset_rate {
coefficient: "98522167480000000000"
scale: 10
}
expiry: 1739790752
min_transportable_units: 34876
}
invoice_result {
r_hash: "\020 \253\033\232\004\355\3019]\270\322\rG\307\277]\253b\304\030\2643\222\037\274\202h\2073\262s"
payment_request: "lnbcrt3654n1pnmxxtapp5zqs2kxu6qnkuzw2ahrfq6378haw6kckyrz6r8yslhjpx3penkfesdqqcqzzsxqyz5vqrzjqf7fkzesd5m79huf73syvzjd7yrn472dpz8syvzn7qgx6rv470ppeaxl25tc4vrvhcqqqqlgqqqqqqgq2qsp5h3whq8pr32p0uckcw770fg3uvnr5rg76xpq3mpmdu9akmjscj88q9qxpqysgq7r2rxcq2amu5kpj5k5ercwp5qz844mngwm3f9sx569qc3dge4q3rt5t4wwz0429uy77xlhulewv274hfgmvs77wut8z7hptd2h2ydeqq0wfmmm"
add_index: 1
payment_addr: "\274]p\034#\212\202\376b\330w\274\364\242<d\307A\243\3320A\035\207m\341{m\312\030\221\316"
}
r_hash: 1020ab1b9a04edc1395db8d20d47c7bf5dab62c418b433921fbc82688733b273
invoice asset_amount: 35960
invoice num_satoshis: 365
invoice route_hints: [hop_hints {
node_id: "027c9b0b306d37e2df89f460460a4df1073af94d088f023053f0106d0d95f3c21c"
chan_id: 17644915424660319422
fee_base_msat: 1000
fee_proportional_millionths: 1
cltv_expiry_delta: 80
}
]
expected route: ['edward', 'frank']
actual channel capacities:
active: true
remote_pubkey: "03d6ae1074144880117932cd354d12412930f64e55e62c1fe305b6ec1c578abf38"
channel_point: "174015ce901962a9317e48335857a21c714dcabbb0382b58c89274a253a6a4ca:0"
chan_id: 485984139542528
capacity: 100000
local_balance: 46920
remote_balance: 50000
commit_fee: 2420
commit_weight: 958
fee_per_kw: 2500
csv_delay: 144
private: true
initiator: true
chan_status_flags: "ChanStatusDefault"
local_chan_reserve_sat: 1000
remote_chan_reserve_sat: 1062
lifetime: 9
uptime: 9
commitment_type: SIMPLE_TAPROOT_OVERLAY
push_amount_sat: 50000
local_constraints {
csv_delay: 144
chan_reserve_sat: 1000
dust_limit_sat: 354
max_pending_amt_msat: 99000000
min_htlc_msat: 1
max_accepted_htlcs: 83
}
remote_constraints {
csv_delay: 144
chan_reserve_sat: 1062
dust_limit_sat: 354
max_pending_amt_msat: 99000000
min_htlc_msat: 1
max_accepted_htlcs: 83
}
alias_scids: 17592186044416000001
alias_scids: 17644915424660319422
peer_scid_alias: 17592186044416000000
custom_channel_data: "{\"assets\":[{\"asset_utxo\":{\"version\":1,\"asset_genesis\":{\"genesis_point\":\"2587f694b70a20066162b751bfc341584af591e6ff6f2bd443db410666291b7c:1\",\"name\":\"Asset1\",\"meta_hash\":\"009dc0aa29bd1c320fa9b33e15227169f23b77ab6ca845fabcdab2249b95568c\",\"asset_id\":\"af87bf249a7e5ee330c1210e51ac101224034b2e2716177d210b60a06f714ae7\"},\"amount\":200000000,\"script_key\":\"0250aaeb166f4234650d84a2d8a130987aeaf6950206e0905401ee74ff3f8d18e6\",\"decimal_display\":2},\"capacity\":200000000,\"local_balance\":200000000,\"remote_balance\":0}]}"
custom_channel_data:
{'assets': [{'asset_utxo': {'amount': 200000000,
'asset_genesis': {'asset_id': 'af87bf249a7e5ee330c1210e51ac101224034b2e2716177d210b60a06f714ae7',
'genesis_point': '2587f694b70a20066162b751bfc341584af591e6ff6f2bd443db410666291b7c:1',
'meta_hash': '009dc0aa29bd1c320fa9b33e15227169f23b77ab6ca845fabcdab2249b95568c',
'name': 'Asset1'},
'decimal_display': 2,
'script_key': '0250aaeb166f4234650d84a2d8a130987aeaf6950206e0905401ee74ff3f8d18e6',
'version': 1},
'capacity': 200000000,
'local_balance': 200000000,
'remote_balance': 0}]}
(✔) (A) cap: 100000 sat, edward->[bal: 46920 sat|res: 1000 sat|spend: 41390 sat], frank->[bal: 50000 sat|res: 1062 sat|spend: 44408 sat], commit_fee: 2420
(✔) (A) cap: 200000000 Asset1, edward->[bal: 200000000 Asset1], frank->[bal: 0 Asset1]
accepted_sell_order {
peer: "03d6ae1074144880117932cd354d12412930f64e55e62c1fe305b6ec1c578abf38"
id: "S\255\364\001a\222\263\005\325\333\245\277{\010\035\001M\277\337n0\373\n_\366\022\371\301l\212yt"
scid: 17731509292056082804
asset_amount: 38204
bid_asset_rate {
coefficient: "101499999990000000000"
scale: 10
}
expiry: 1739790752
min_transportable_msat: 354009
}
payment_result {
payment_hash: "1020ab1b9a04edc1395db8d20d47c7bf5dab62c418b433921fbc82688733b273"
value: 365
creation_date: 1739790717
payment_preimage: "0000000000000000000000000000000000000000000000000000000000000000"
value_sat: 365
value_msat: 365400
payment_request: "lnbcrt3654n1pnmxxtapp5zqs2kxu6qnkuzw2ahrfq6378haw6kckyrz6r8yslhjpx3penkfesdqqcqzzsxqyz5vqrzjqf7fkzesd5m79huf73syvzjd7yrn472dpz8syvzn7qgx6rv470ppeaxl25tc4vrvhcqqqqlgqqqqqqgq2qsp5h3whq8pr32p0uckcw770fg3uvnr5rg76xpq3mpmdu9akmjscj88q9qxpqysgq7r2rxcq2amu5kpj5k5ercwp5qz844mngwm3f9sx569qc3dge4q3rt5t4wwz0429uy77xlhulewv274hfgmvs77wut8z7hptd2h2ydeqq0wfmmm"
status: IN_FLIGHT
creation_time_ns: 1739790717487880562
payment_index: 1
first_hop_custom_records {
key: 65538
value: "S\255\364\001a\222\263\005\325\333\245\277{\010\035\001M\277\337n0\373\n_\366\022\371\301l\212yt"
}
}
payment_result {
payment_hash: "1020ab1b9a04edc1395db8d20d47c7bf5dab62c418b433921fbc82688733b273"
value: 365
creation_date: 1739790717
payment_preimage: "0000000000000000000000000000000000000000000000000000000000000000"
value_sat: 365
value_msat: 365400
payment_request: "lnbcrt3654n1pnmxxtapp5zqs2kxu6qnkuzw2ahrfq6378haw6kckyrz6r8yslhjpx3penkfesdqqcqzzsxqyz5vqrzjqf7fkzesd5m79huf73syvzjd7yrn472dpz8syvzn7qgx6rv470ppeaxl25tc4vrvhcqqqqlgqqqqqqgq2qsp5h3whq8pr32p0uckcw770fg3uvnr5rg76xpq3mpmdu9akmjscj88q9qxpqysgq7r2rxcq2amu5kpj5k5ercwp5qz844mngwm3f9sx569qc3dge4q3rt5t4wwz0429uy77xlhulewv274hfgmvs77wut8z7hptd2h2ydeqq0wfmmm"
status: IN_FLIGHT
creation_time_ns: 1739790717487880562
htlcs {
route {
total_time_lock: 734
total_amt: 365
hops {
chan_id: 485984139542528
chan_capacity: 100000
amt_to_forward: 365
expiry: 734
amt_to_forward_msat: 365400
pub_key: "03d6ae1074144880117932cd354d12412930f64e55e62c1fe305b6ec1c578abf38"
tlv_payload: true
mpp_record {
total_amt_msat: 365400
payment_addr: "\274]p\034#\212\202\376b\330w\274\364\242<d\307A\243\3320A\035\207m\341{m\312\030\221\316"
}
}
total_amt_msat: 365400
first_hop_amount_msat: 354000
custom_channel_data: "{\"balances\":[{\"asset_id\":\"af87bf249a7e5ee330c1210e51ac101224034b2e2716177d210b60a06f714ae7\",\"amount\":37088}],\"rfq_id\":\"53adf4016192b305d5dba5bf7b081d014dbfdf6e30fb0a5ff612f9c16c8a7974\"}"
}
attempt_time_ns: 1739790717502996180
attempt_id: 1
}
payment_index: 1
first_hop_custom_records {
key: 65538
value: "S\255\364\001a\222\263\005\325\333\245\277{\010\035\001M\277\337n0\373\n_\366\022\371\301l\212yt"
}
}
payment_result {
payment_hash: "1020ab1b9a04edc1395db8d20d47c7bf5dab62c418b433921fbc82688733b273"
value: 365
creation_date: 1739790717
payment_preimage: "ac829941a25edfda67b517b4f41da14183225cbac80e5c2eaec2b3c74de2c29a"
value_sat: 365
value_msat: 365400
payment_request: "lnbcrt3654n1pnmxxtapp5zqs2kxu6qnkuzw2ahrfq6378haw6kckyrz6r8yslhjpx3penkfesdqqcqzzsxqyz5vqrzjqf7fkzesd5m79huf73syvzjd7yrn472dpz8syvzn7qgx6rv470ppeaxl25tc4vrvhcqqqqlgqqqqqqgq2qsp5h3whq8pr32p0uckcw770fg3uvnr5rg76xpq3mpmdu9akmjscj88q9qxpqysgq7r2rxcq2amu5kpj5k5ercwp5qz844mngwm3f9sx569qc3dge4q3rt5t4wwz0429uy77xlhulewv274hfgmvs77wut8z7hptd2h2ydeqq0wfmmm"
status: SUCCEEDED
creation_time_ns: 1739790717487880562
htlcs {
status: SUCCEEDED
route {
total_time_lock: 734
total_amt: 365
hops {
chan_id: 485984139542528
chan_capacity: 100000
amt_to_forward: 365
expiry: 734
amt_to_forward_msat: 365400
pub_key: "03d6ae1074144880117932cd354d12412930f64e55e62c1fe305b6ec1c578abf38"
tlv_payload: true
mpp_record {
total_amt_msat: 365400
payment_addr: "\274]p\034#\212\202\376b\330w\274\364\242<d\307A\243\3320A\035\207m\341{m\312\030\221\316"
}
}
total_amt_msat: 365400
first_hop_amount_msat: 354000
custom_channel_data: "{\"balances\":[{\"asset_id\":\"af87bf249a7e5ee330c1210e51ac101224034b2e2716177d210b60a06f714ae7\",\"amount\":37088}],\"rfq_id\":\"53adf4016192b305d5dba5bf7b081d014dbfdf6e30fb0a5ff612f9c16c8a7974\"}"
}
attempt_time_ns: 1739790717502996180
resolve_time_ns: 1739790717656241145
preimage: "\254\202\231A\242^\337\332g\265\027\264\364\035\241A\203\"\\\272\310\016\\.\256\302\263\307M\342\302\232"
attempt_id: 1
}
payment_index: 1
first_hop_custom_records {
key: 65538
value: "S\255\364\001a\222\263\005\325\333\245\277{\010\035\001M\277\337n0\373\n_\366\022\371\301l\212yt"
}
}
status: SUCCEEDED
expected route: ['edward', 'frank']
actual channel capacities:
active: true
remote_pubkey: "03d6ae1074144880117932cd354d12412930f64e55e62c1fe305b6ec1c578abf38"
channel_point: "174015ce901962a9317e48335857a21c714dcabbb0382b58c89274a253a6a4ca:0"
chan_id: 485984139542528
capacity: 100000
local_balance: 46566
remote_balance: 50354
commit_fee: 2420
commit_weight: 958
fee_per_kw: 2500
total_satoshis_sent: 354
num_updates: 2
csv_delay: 144
private: true
initiator: true
chan_status_flags: "ChanStatusDefault"
local_chan_reserve_sat: 1000
remote_chan_reserve_sat: 1062
lifetime: 11
uptime: 11
commitment_type: SIMPLE_TAPROOT_OVERLAY
push_amount_sat: 50000
local_constraints {
csv_delay: 144
chan_reserve_sat: 1000
dust_limit_sat: 354
max_pending_amt_msat: 99000000
min_htlc_msat: 1
max_accepted_htlcs: 83
}
remote_constraints {
csv_delay: 144
chan_reserve_sat: 1062
dust_limit_sat: 354
max_pending_amt_msat: 99000000
min_htlc_msat: 1
max_accepted_htlcs: 83
}
alias_scids: 17592186044416000001
alias_scids: 17644915424660319422
peer_scid_alias: 17592186044416000000
custom_channel_data: "{\"assets\":[{\"asset_utxo\":{\"version\":1,\"asset_genesis\":{\"genesis_point\":\"2587f694b70a20066162b751bfc341584af591e6ff6f2bd443db410666291b7c:1\",\"name\":\"Asset1\",\"meta_hash\":\"009dc0aa29bd1c320fa9b33e15227169f23b77ab6ca845fabcdab2249b95568c\",\"asset_id\":\"af87bf249a7e5ee330c1210e51ac101224034b2e2716177d210b60a06f714ae7\"},\"amount\":200000000,\"script_key\":\"0250aaeb166f4234650d84a2d8a130987aeaf6950206e0905401ee74ff3f8d18e6\",\"decimal_display\":2},\"capacity\":200000000,\"local_balance\":199962912,\"remote_balance\":37088}]}"
custom_channel_data:
{'assets': [{'asset_utxo': {'amount': 200000000,
'asset_genesis': {'asset_id': 'af87bf249a7e5ee330c1210e51ac101224034b2e2716177d210b60a06f714ae7',
'genesis_point': '2587f694b70a20066162b751bfc341584af591e6ff6f2bd443db410666291b7c:1',
'meta_hash': '009dc0aa29bd1c320fa9b33e15227169f23b77ab6ca845fabcdab2249b95568c',
'name': 'Asset1'},
'decimal_display': 2,
'script_key': '0250aaeb166f4234650d84a2d8a130987aeaf6950206e0905401ee74ff3f8d18e6',
'version': 1},
'capacity': 200000000,
'local_balance': 199962912,
'remote_balance': 37088}]}
(A) cap: 100000 sat, edward->[bal: 46566 sat|res: 1000 sat|spend: 41036 sat], frank->[bal: 50354 sat|res: 1062 sat|spend: 44762 sat], commit_fee: 2420
(A) cap: 200000000 Asset1, edward->[bal: 199962912 Asset1], frank->[bal: 37088 Asset1]
=======================================================================================
Seems like this is a bug with the RFQ system because
In [1]: 101499999990000000000/98522167480000000000*36000
Out[1]: 37088.09999923887
I'm not sure why RFQ is needed at all for direct asset transfers.
If an invoice is involved, there's always an RFQ process, even for direct channels. Because the amount in the invoice is always in satoshi.
If an invoice is involved, there's always an RFQ process, even for direct channels. Because the amount in the invoice is always in satoshi.
That can make sense, but do you agree there is still a bug? I don't see how this behaviour can be explained from a design standpoint.
I'm not as skilled with reading the output of the test framework yet... So I'm sorry if this question can be answered with the output above: Is this using a price oracle with a spread? And if yes, can the difference in asset units be explained by the spread?
That can make sense, but do you agree there is still a bug?
I don't know. Asset channels weren't designed with direct-channel payments in mind. Using an invoice means maximum compatibility with the network, so the unit is always satoshi. Also see discussion here: https://github.com/lightninglabs/taproot-assets/issues/1275#issuecomment-2656126366
If you want to send a direct peer payment, why don't you send a keysend payment? Or encode the send amount directly?
I'm not as skilled with reading the output of the test framework yet... So I'm sorry if this question can be answered with the output above: Is this using a price oracle with a spread? And if yes, can the difference in asset units be explained by the spread?
Yes, there is a spread in the buy/sell price, but I'm not buying or selling here. As mentioned in https://github.com/lightninglabs/taproot-assets/issues/1392#issuecomment-2662848038 , it looks like the ratio of the buy/sell price is the error I'm seeing.
I don't know. Asset channels weren't designed with direct-channel payments in mind. Using an invoice means maximum compatibility with the network, so the unit is always satoshi. Also see discussion here: #1275 (comment)
If you don't think direct payments are a design goal and if this behaviour is going to be allowed, I think they should be disallowed. However, I think they should be allowed and this behaviour fixed.
If you want to send a direct peer payment, why don't you send a keysend payment?
Keysend does not allow the normal benefits of an invoice.
Or encode the send amount directly?
Don't know what you mean.
However, I think they should be allowed and this behaviour fixed.
So you mean we should add custom logic to tapd that detects and handles payments to a direct peer differently?
We probably could use the same RFQ quote that was used to create the invoice on the sender side instead of creating a new one on the same channel but in the opposite direction.
But we'd still potentially get an off-by-one error in that case, since a conversion from asset units to sats (and back) would still occur, just not with a spread.
I think an off by one error is a lot better than being off by a factor of BuyRate/SellRate.
Might be best to just leave this issue open for a while to see if other people are as confused as I am when trying to make direct peer payments?
I think an off by one error is a lot better than being off by a factor of BuyRate/SellRate.
Might be best to just leave this issue open for a while to see if other people are as confused as I am when trying to make direct peer payments?
We are experiencing the same issue, which is particularly prominent when there is a direct channel. This has a very direct impact on our service promotion. Adding custom logic for payments or RFQs is essential.
Renaming this issue to clarify the expected vs seen behavior here.
Another scenario we need to consider:
TA TA
edward <----> frank <----> irina
In this case, do we expect frank to collect routing fees in terms of sats or just a buy/sell spread, or both?
Seems to me like it would be routing fees in terms of assets, having a proportional and base fee, just like sats routing fees. I'm thinking this because frank does not have any market risk for just forwarding a taproot asset. However, in order to do this, there would need to be a special option for frank to charge a different RFQ/price oracle buy/sell rate based on what channel the payment is routed out of?
Here, I'm assuming that both taproot asset channels are trading the same asset. If they used different assets, I think there should be a buy/sell spread because there will be market risk exchanging different assets.
In this case, do we expect frank to collect routing fees in terms of sats or just a buy/sell spread, or both?
Imagine that it's all just normal sats channels. Frank collects fees as he'll send out less to irina than he received from edward. Edward will go to pay the invoice, once path finding has finished, he'll see a final value that's greater than the stated invoice amount (in sats). The difference between those two values is the fee.
Ah I think I misunderstood the core question:
However, in order to do this, there would need to be a special option for frank to charge a different RFQ/price oracle buy/sell rate based on what channel the payment is routed out of?
In theory that could be implemented using: https://github.com/lightninglabs/taproot-assets/issues/1362.
Also the way things work is that Frank communicates to Irina what his routing policy is. Irina the puts that into the hop hints that go in her invoice. If she tries to swap things out, Frank will reject a forward (eg: doesn't pay enough fees, etc).
I think the main difference is that the current price oracle is a great design for payments, as it integrates well with the Lightning Network. On the other hand, direct channels are more suited for transfers, such as moving USDT in and out of a CEX. From a user experience perspective, transfers require more certainty in the received amount.
Also the way things work is that Frank communicates to Irina what his routing policy is. Irina the puts that into the hop hints that go in her invoice. If she tries to swap things out, Frank will reject a forward (eg: doesn't pay enough fees, etc).
Yeah, I think the thing here is it is unclear how this is implemented (if at all) for the edward-frank-irina case to clearly control this.
such as moving USDT in and out of a CEX. From a user experience perspective, transfers require more certainty in the received amount.
True, but what do we need a CEX for if we have native taproot assets support in the lightning network? Taproot assets provides us a DEX instead.
I do think we need more certainty on the sent and received amount for all types of payments though. The following issues also try to help with the uncertainty:
- https://github.com/lightninglabs/taproot-assets/issues/1366
- https://github.com/lightninglabs/taproot-assets/issues/1367
- https://github.com/lightninglabs/taproot-assets/issues/1368