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

[bug]: asset amount received off by 1

Open ZZiigguurraatt opened this issue 10 months ago • 9 comments

I have the following network configuration:

       SAT           TA
dave <----> edward <----> frank

Frank is trying to receive 36000 asset. Frank creates an invoice and then dave pays it, but only 35999 asset are received by frank. Here's the output of my test script:

=======================================================================================
dave sending sats and frank receiving 36000 Asset1_base (360.0 Asset1_friendly) via edward
MaxAllowableFee: 11
accepted_buy_quote {
  peer: "0363288fc037049c3129c79e9d0fe5fe39fd9a81086d052bd7e2260184f93dc0bf"
  id: "I!\317\337\326YT\306\316c\204\220\\\027W\275\025Ra\212\322\262Z\330\364yt\212\027~*n"
  scid: 17616239553884858990
  asset_max_amount: 36000
  ask_asset_rate {
    coefficient: "98522167480000000000"
    scale: 10
  }
  expiry: 1739790310
  min_transportable_units: 34876
}
invoice_result {
  r_hash: "\272\336\007\243\2747\334G\363V\247\"\031\243n8\352h?J\034\362\025\013\'\334\252%\024\346A\010"
  payment_request: "lnbcrt3654n1pnmx97rpp5ht0q0gauxlwy0u6k5u3pngmw8r4xs062rnep2ze8mj4z298xgyyqdqqcqzzsxqyz5vqrzjqd3j3r7qxuzfcvffc70f6rl9lculmx5pppks227hugnqrp8e8hqtlarewj9pwl32dcqqqqlgqqqqqqgq2qsp57hdvujmvs96knjm8yky24emrxx7sd8dfx3zcpw3eva8c7fc5tffq9qxpqysgqerguxmewt0fwl338zhp7rxhmdzwgpwuqc3f73q0uf5hu2wr2p6ax4lg6q79ga44l9wjsmx9lfhhm6lh0ar7aw4edn3k59g5ysyavzqcqr0wtpw"
  add_index: 1
  payment_addr: "\365\332\316Kl\201ui\313g%\210\252\347c1\275\006\235\2514E\200\2729gO\217\'\024ZR"
}

r_hash: bade07a3bc37dc47f356a72219a36e38ea683f4a1cf2150b27dcaa2514e64108

invoice num_satoshis: 365

invoice route_hints: [hop_hints {
  node_id: "0363288fc037049c3129c79e9d0fe5fe39fd9a81086d052bd7e2260184f93dc0bf"
  chan_id: 17616239553884858990
  fee_base_msat: 1000
  fee_proportional_millionths: 1
  cltv_expiry_delta: 80
}
]

expected route: ['dave', 'edward', 'frank']
actual channel capacities:
active: true
remote_pubkey: "0363288fc037049c3129c79e9d0fe5fe39fd9a81086d052bd7e2260184f93dc0bf"
channel_point: "ce67b84f7c2dca9088681ab3ee54ef0ba24d989af39831b3a0fa04a09f4bfc01:0"
chan_id: 387028093042688
capacity: 1000000
local_balance: 946530
remote_balance: 50000
commit_fee: 2810
commit_weight: 1116
fee_per_kw: 2500
csv_delay: 144
initiator: true
chan_status_flags: "ChanStatusDefault"
local_chan_reserve_sat: 10000
remote_chan_reserve_sat: 10000
lifetime: 80
uptime: 80
commitment_type: ANCHORS
push_amount_sat: 50000
local_constraints {
  csv_delay: 144
  chan_reserve_sat: 10000
  dust_limit_sat: 354
  max_pending_amt_msat: 990000000
  min_htlc_msat: 1
  max_accepted_htlcs: 483
}
remote_constraints {
  csv_delay: 144
  chan_reserve_sat: 10000
  dust_limit_sat: 354
  max_pending_amt_msat: 990000000
  min_htlc_msat: 1
  max_accepted_htlcs: 483
}
alias_scids: 17592186044416000001
peer_scid_alias: 17592186044416000000

(✔) (A) cap: 1000000 sat, dave->[bal: 946530 sat|res: 10000 sat|spend: 932000 sat], edward->[bal: 50000 sat|res: 10000 sat|spend: 35470 sat], commit_fee: 2810
active: true
remote_pubkey: "03e18a034b585fd7596c95845d25f555a815c46afb191fb0bc7d5107c84b27f046"
channel_point: "20f5b113f3fb116d3eadf3b7aa4273464b8c6575be22586146ddd7b803c2374c: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: 36
uptime: 36
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: 17616239553884858990
peer_scid_alias: 17592186044416000000
custom_channel_data: "{\"assets\":[{\"asset_utxo\":{\"version\":1,\"asset_genesis\":{\"genesis_point\":\"e7a9d5de366f27522d4ba930e6a609a3510ddb2094a4440d6dbfee1f0f8fabcb:1\",\"name\":\"Asset1\",\"meta_hash\":\"009dc0aa29bd1c320fa9b33e15227169f23b77ab6ca845fabcdab2249b95568c\",\"asset_id\":\"37b7a42f889d90f46f6208da31997ffd15d8b06dfa2e56ee0f3c367de0b08089\"},\"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': '37b7a42f889d90f46f6208da31997ffd15d8b06dfa2e56ee0f3c367de0b08089',
                                              'genesis_point': 'e7a9d5de366f27522d4ba930e6a609a3510ddb2094a4440d6dbfee1f0f8fabcb: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]

payment_hash: "bade07a3bc37dc47f356a72219a36e38ea683f4a1cf2150b27dcaa2514e64108"
value: 365
creation_date: 1739790275
payment_preimage: "0000000000000000000000000000000000000000000000000000000000000000"
value_sat: 365
value_msat: 365400
payment_request: "lnbcrt3654n1pnmx97rpp5ht0q0gauxlwy0u6k5u3pngmw8r4xs062rnep2ze8mj4z298xgyyqdqqcqzzsxqyz5vqrzjqd3j3r7qxuzfcvffc70f6rl9lculmx5pppks227hugnqrp8e8hqtlarewj9pwl32dcqqqqlgqqqqqqgq2qsp57hdvujmvs96knjm8yky24emrxx7sd8dfx3zcpw3eva8c7fc5tffq9qxpqysgqerguxmewt0fwl338zhp7rxhmdzwgpwuqc3f73q0uf5hu2wr2p6ax4lg6q79ga44l9wjsmx9lfhhm6lh0ar7aw4edn3k59g5ysyavzqcqr0wtpw"
status: IN_FLIGHT
creation_time_ns: 1739790275326967912
payment_index: 1

payment_hash: "bade07a3bc37dc47f356a72219a36e38ea683f4a1cf2150b27dcaa2514e64108"
value: 365
creation_date: 1739790275
payment_preimage: "0000000000000000000000000000000000000000000000000000000000000000"
value_sat: 365
value_msat: 365400
payment_request: "lnbcrt3654n1pnmx97rpp5ht0q0gauxlwy0u6k5u3pngmw8r4xs062rnep2ze8mj4z298xgyyqdqqcqzzsxqyz5vqrzjqd3j3r7qxuzfcvffc70f6rl9lculmx5pppks227hugnqrp8e8hqtlarewj9pwl32dcqqqqlgqqqqqqgq2qsp57hdvujmvs96knjm8yky24emrxx7sd8dfx3zcpw3eva8c7fc5tffq9qxpqysgqerguxmewt0fwl338zhp7rxhmdzwgpwuqc3f73q0uf5hu2wr2p6ax4lg6q79ga44l9wjsmx9lfhhm6lh0ar7aw4edn3k59g5ysyavzqcqr0wtpw"
status: IN_FLIGHT
creation_time_ns: 1739790275326967912
htlcs {
  route {
    total_time_lock: 814
    total_fees: 1
    total_amt: 366
    hops {
      chan_id: 387028093042688
      chan_capacity: 1000000
      amt_to_forward: 365
      fee: 1
      expiry: 734
      amt_to_forward_msat: 365400
      fee_msat: 1000
      pub_key: "0363288fc037049c3129c79e9d0fe5fe39fd9a81086d052bd7e2260184f93dc0bf"
      tlv_payload: true
    }
    hops {
      chan_id: 17616239553884858990
      chan_capacity: 365
      amt_to_forward: 365
      expiry: 734
      amt_to_forward_msat: 365400
      pub_key: "03e18a034b585fd7596c95845d25f555a815c46afb191fb0bc7d5107c84b27f046"
      tlv_payload: true
      mpp_record {
        total_amt_msat: 365400
        payment_addr: "\365\332\316Kl\201ui\313g%\210\252\347c1\275\006\235\2514E\200\2729gO\217\'\024ZR"
      }
    }
    total_fees_msat: 1000
    total_amt_msat: 366400
    first_hop_amount_msat: 366400
  }
  attempt_time_ns: 1739790275341403191
  attempt_id: 1
}
payment_index: 1

payment_hash: "bade07a3bc37dc47f356a72219a36e38ea683f4a1cf2150b27dcaa2514e64108"
value: 365
creation_date: 1739790275
fee: 1
payment_preimage: "336a8fd91c848fbdf373a8902f7fc765a678faec207185d10277a18ad8db45d0"
value_sat: 365
value_msat: 365400
payment_request: "lnbcrt3654n1pnmx97rpp5ht0q0gauxlwy0u6k5u3pngmw8r4xs062rnep2ze8mj4z298xgyyqdqqcqzzsxqyz5vqrzjqd3j3r7qxuzfcvffc70f6rl9lculmx5pppks227hugnqrp8e8hqtlarewj9pwl32dcqqqqlgqqqqqqgq2qsp57hdvujmvs96knjm8yky24emrxx7sd8dfx3zcpw3eva8c7fc5tffq9qxpqysgqerguxmewt0fwl338zhp7rxhmdzwgpwuqc3f73q0uf5hu2wr2p6ax4lg6q79ga44l9wjsmx9lfhhm6lh0ar7aw4edn3k59g5ysyavzqcqr0wtpw"
status: SUCCEEDED
fee_sat: 1
fee_msat: 1000
creation_time_ns: 1739790275326967912
htlcs {
  status: SUCCEEDED
  route {
    total_time_lock: 814
    total_fees: 1
    total_amt: 366
    hops {
      chan_id: 387028093042688
      chan_capacity: 1000000
      amt_to_forward: 365
      fee: 1
      expiry: 734
      amt_to_forward_msat: 365400
      fee_msat: 1000
      pub_key: "0363288fc037049c3129c79e9d0fe5fe39fd9a81086d052bd7e2260184f93dc0bf"
      tlv_payload: true
    }
    hops {
      chan_id: 17616239553884858990
      chan_capacity: 365
      amt_to_forward: 365
      expiry: 734
      amt_to_forward_msat: 365400
      pub_key: "03e18a034b585fd7596c95845d25f555a815c46afb191fb0bc7d5107c84b27f046"
      tlv_payload: true
      mpp_record {
        total_amt_msat: 365400
        payment_addr: "\365\332\316Kl\201ui\313g%\210\252\347c1\275\006\235\2514E\200\2729gO\217\'\024ZR"
      }
    }
    total_fees_msat: 1000
    total_amt_msat: 366400
    first_hop_amount_msat: 366400
  }
  attempt_time_ns: 1739790275341403191
  resolve_time_ns: 1739790275600402040
  preimage: "3j\217\331\034\204\217\275\363s\250\220/\177\307e\246x\372\354 q\205\321\002w\241\212\330\333E\320"
  attempt_id: 1
}
payment_index: 1

status: SUCCEEDED
expected route: ['dave', 'edward', 'frank']
actual channel capacities:
active: true
remote_pubkey: "0363288fc037049c3129c79e9d0fe5fe39fd9a81086d052bd7e2260184f93dc0bf"
channel_point: "ce67b84f7c2dca9088681ab3ee54ef0ba24d989af39831b3a0fa04a09f4bfc01:0"
chan_id: 387028093042688
capacity: 1000000
local_balance: 946163
remote_balance: 50366
commit_fee: 2811
commit_weight: 1116
fee_per_kw: 2500
total_satoshis_sent: 366
num_updates: 2
csv_delay: 144
initiator: true
chan_status_flags: "ChanStatusDefault"
local_chan_reserve_sat: 10000
remote_chan_reserve_sat: 10000
lifetime: 82
uptime: 82
commitment_type: ANCHORS
push_amount_sat: 50000
local_constraints {
  csv_delay: 144
  chan_reserve_sat: 10000
  dust_limit_sat: 354
  max_pending_amt_msat: 990000000
  min_htlc_msat: 1
  max_accepted_htlcs: 483
}
remote_constraints {
  csv_delay: 144
  chan_reserve_sat: 10000
  dust_limit_sat: 354
  max_pending_amt_msat: 990000000
  min_htlc_msat: 1
  max_accepted_htlcs: 483
}
alias_scids: 17592186044416000001
peer_scid_alias: 17592186044416000000

(A) cap: 1000000 sat, dave->[bal: 946163 sat|res: 10000 sat|spend: 931633 sat], edward->[bal: 50366 sat|res: 10000 sat|spend: 35836 sat], commit_fee: 2811
active: true
remote_pubkey: "03e18a034b585fd7596c95845d25f555a815c46afb191fb0bc7d5107c84b27f046"
channel_point: "20f5b113f3fb116d3eadf3b7aa4273464b8c6575be22586146ddd7b803c2374c: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: 38
uptime: 38
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: 17616239553884858990
peer_scid_alias: 17592186044416000000
custom_channel_data: "{\"assets\":[{\"asset_utxo\":{\"version\":1,\"asset_genesis\":{\"genesis_point\":\"e7a9d5de366f27522d4ba930e6a609a3510ddb2094a4440d6dbfee1f0f8fabcb:1\",\"name\":\"Asset1\",\"meta_hash\":\"009dc0aa29bd1c320fa9b33e15227169f23b77ab6ca845fabcdab2249b95568c\",\"asset_id\":\"37b7a42f889d90f46f6208da31997ffd15d8b06dfa2e56ee0f3c367de0b08089\"},\"amount\":200000000,\"script_key\":\"0250aaeb166f4234650d84a2d8a130987aeaf6950206e0905401ee74ff3f8d18e6\",\"decimal_display\":2},\"capacity\":200000000,\"local_balance\":199964001,\"remote_balance\":35999}]}"

custom_channel_data:
{'assets': [{'asset_utxo': {'amount': 200000000,
                            'asset_genesis': {'asset_id': '37b7a42f889d90f46f6208da31997ffd15d8b06dfa2e56ee0f3c367de0b08089',
                                              'genesis_point': 'e7a9d5de366f27522d4ba930e6a609a3510ddb2094a4440d6dbfee1f0f8fabcb:1',
                                              'meta_hash': '009dc0aa29bd1c320fa9b33e15227169f23b77ab6ca845fabcdab2249b95568c',
                                              'name': 'Asset1'},
                            'decimal_display': 2,
                            'script_key': '0250aaeb166f4234650d84a2d8a130987aeaf6950206e0905401ee74ff3f8d18e6',
                            'version': 1},
             'capacity': 200000000,
             'local_balance': 199964001,
             'remote_balance': 35999}]}
(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: 199964001 Asset1], frank->[bal: 35999 Asset1]

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

ZZiigguurraatt avatar Feb 17 '25 11:02 ZZiigguurraatt

See https://github.com/lightninglabs/taproot-assets/blob/main/docs/rfq-and-decimal-display.md#precision-requirement-for-assets-in-the-lightning-network

guggero avatar Feb 17 '25 11:02 guggero

OK, so it seems you don't work in sat

In [1]: AssetsPerSat=(98522167480000000000/10**10)/100e6

In [2]: AssetsPerSat
Out[2]: 98.52216748

In [3]: Sats=36000/AssetsPerSat

In [4]: Sats
Out[4]: 365.4000000285012

In [5]: int(Sats)
Out[5]: 365

In [6]: int(Sats)*AssetsPerSat
Out[6]: 35960.591130199995

but do work in msat

In [1]: AssetsPerMSat=(98522167480000000000/10**10)/100e9

In [2]: AssetsPerMSat
Out[2]: 0.09852216748

In [3]: MSat=36000/AssetsPerMSat

In [4]: MSat
Out[4]: 365400.0000285012

In [5]: int(MSat)
Out[5]: 365400

In [6]: int(MSat)*AssetsPerMSat
Out[6]: 35999.999997192004

In [7]: int(int(MSat)*AssetsPerMSat)
Out[7]: 35999

?

ZZiigguurraatt avatar Feb 17 '25 11:02 ZZiigguurraatt

Yes, the fundamental unit of the LN is milli-satoshi.

guggero avatar Feb 17 '25 11:02 guggero

OK, so the problem here is that internally asset units are always derived units and so we loose precision when going back and forth?

So, this is basically a won't fix?

ZZiigguurraatt avatar Feb 17 '25 12:02 ZZiigguurraatt

Yes, it's a fundamental issue of not having true fractional units and exchange rate calculations. If your calculation for something is 35999.99999 and need to represent that as an integer, you have two options: Either you round down (e.g. sender sends one unit less) or you round up (sender potentially ends one unit too much).

We've decided to go with "allow underpayment by at most 1 unit by sender" instead of the other possibility which would be "always overpay by at most 1 unit on sender". These are the two possibilities we have. So IMO discussion should be: Which one is preferred?

guggero avatar Feb 17 '25 13:02 guggero

So IMO discussion should be: Which one is preferred?

So, it seems you are only giving the option of ceil vs floor as a rounding technique, but why not also allow rounding up if greater or equal to 1/2 fractional assets and rounding down if less than 1/2 fractional assets? That case would make more sense to me if we have a FixedPoint type that has many more digits that can capture fractional assets in it's arithmetic before converting back to an integer.

ZZiigguurraatt avatar Feb 17 '25 15:02 ZZiigguurraatt

So, it seems you are only giving the option of ceil vs floor as a rounding technique, but why not also allow rounding up if greater or equal to 1/2 fractional assets and rounding down if less than 1/2 fractional assets?

How does that differ from ceil vs floor?

Or is it that you suggest a piece-wise rounding scheme? I think we'd need to carefully analyze the proposal.

If we're always slowly rounding up anytime something is above 1/2 fractional cents, then I would be worried about the consistent upward drift that could include.

Roasbeef avatar Feb 18 '25 01:02 Roasbeef

So, it seems you are only giving the option of ceil vs floor as a rounding technique, but why not also allow rounding up if greater or equal to 1/2 fractional assets and rounding down if less than 1/2 fractional assets?

How does that differ from ceil vs floor?

Or is it that you suggest a piece-wise rounding scheme? I think we'd need to carefully analyze the proposal.

If we're always slowly rounding up anytime something is above 1/2 fractional cents, then I would be worried about the consistent upward drift that could include.

ceil and floor are off by up to 1, whereas rounding at 1/2, you are off by at most 1/2.

If there is already a standard throughout all LL products to use floor, then maybe it is best with that to be consistent though.

ZZiigguurraatt avatar Feb 18 '25 16:02 ZZiigguurraatt

@Liongrass , one thing that may not be as heavily emphasized in the docs as it should be is that sats are the base of everything, even if we think we are working in assets. Even if we are doing a direct asset only channel, assets are still a derived unit so we have other weird stuff that comes up like https://github.com/lightninglabs/taproot-assets/issues/1392 .

ZZiigguurraatt avatar Feb 18 '25 16:02 ZZiigguurraatt

Seems like this is a won't fix, gonna close this.

levmi avatar Jun 09 '25 14:06 levmi