taproot-assets icon indicating copy to clipboard operation
taproot-assets copied to clipboard

[bug]: direct payments of an asset invoice uses remote peer's quote

Open ZZiigguurraatt opened this issue 9 months ago • 15 comments

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]

=======================================================================================

ZZiigguurraatt avatar Feb 17 '25 11:02 ZZiigguurraatt

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.

ZZiigguurraatt avatar Feb 17 '25 11:02 ZZiigguurraatt

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.

guggero avatar Feb 17 '25 11:02 guggero

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.

ZZiigguurraatt avatar Feb 17 '25 11:02 ZZiigguurraatt

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?

guggero avatar Feb 17 '25 12:02 guggero

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.

ZZiigguurraatt avatar Feb 17 '25 12:02 ZZiigguurraatt

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.

guggero avatar Feb 17 '25 13:02 guggero

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?

ZZiigguurraatt avatar Feb 17 '25 15:02 ZZiigguurraatt

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.

lukegao209 avatar Feb 24 '25 05:02 lukegao209

Renaming this issue to clarify the expected vs seen behavior here.

Roasbeef avatar Feb 25 '25 02:02 Roasbeef

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.

ZZiigguurraatt avatar Feb 28 '25 19:02 ZZiigguurraatt

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.

Roasbeef avatar Mar 06 '25 00:03 Roasbeef

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).

Roasbeef avatar Mar 06 '25 00:03 Roasbeef

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.

lukegao209 avatar Mar 06 '25 02:03 lukegao209

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.

ZZiigguurraatt avatar Mar 07 '25 18:03 ZZiigguurraatt

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

ZZiigguurraatt avatar Mar 07 '25 18:03 ZZiigguurraatt