Page MenuHomePhabricator

[Cashtab] Only add inputs to a tx if you need them
ClosedPublic

Authored by bytesofman on Jul 9 2024, 20:28.

Details

Reviewers
emack
Group Reviewers
Restricted Project
Commits
rABC2537cd1d3835: [Cashtab] Only add inputs to a tx if you need them
Summary

NFT support will introduce the first txs in Cashtab with a required input that is not a token input.

Generalize the sendXec function to accept requiredInputs as a param (really, just renaming tokenInputs).

Because the agora input includes XEC, the tx will typically not require additional utxos. Refactor sendXec so that we do not add utxos if we already have sufficient utxos for the tx.

In implementing this refactor, discovered it is also an optimization -- some burn txs will have enough utxos from token dust inputs and will not need an additional non-SLP utxo.

Update these burn tests. Add a new test for edge case where sendXec tries to build and broadcast a tx but needs to add another utxo to cover fee.

Test Plan

npm test

Diff Detail

Repository
rABC Bitcoin ABC
Branch
more-sendxec-refactors
Lint
Lint Passed
Unit
No Test Coverage
Build Status
Buildable 29510
Build 58556: Build Diff
Build 58555: arc lint + arc unit

Event Timeline

add another test to confirm insufficient funds error also pans out in edge case, remove debug log

bytesofman published this revision for review.Jul 9 2024, 21:05
bytesofman added inline comments.
cashtab/src/transactions/fixtures/vectors.js
628 ↗(On Diff #48560)

In a token burn tx, you end up having enough XEC from your dust inputs

TIL

Here's the tx in the test after this change

image.png (702×792 px, 71 KB)

Here's the tx before this change. Note we add a nonSlpUtxo automatically to all txs before this refactor, which is overspending on some burn txs.

image.png (667×792 px, 82 KB)

cashtab/src/transactions/index.js
53 ↗(On Diff #48560)

see D16437, change backed out of this diff

emack requested changes to this revision.Jul 10 2024, 11:38
emack added a subscriber: emack.
emack added inline comments.
cashtab/src/transactions/index.js
152 ↗(On Diff #48560)

At this point once all UTXOs have been parsed and there is not enough to cover the fee, but rather than relying on TxBuilder below to throw an exception wouldn't it be cleaner and more reliable to explicitly check for it here?

e.g. change the loop to one that tracks the index and if index = spendableUtxos.length && needsAnotherUtxo then throw exception here.

Otherwise contributors to ecash-lib might not be aware of the dependency and update the error strings and inadvertently breaking this logic here.

161 ↗(On Diff #48560)

Should be referring to dustSats in config/app.js. Also shouldn't this be dynamic based on presence of GRP tokens?

This revision now requires changes to proceed.Jul 10 2024, 11:38
cashtab/src/transactions/index.js
152 ↗(On Diff #48560)

correction... if index = spendableUtxos.length && !needsAnotherUtxo && (inputSatoshis - satoshisToSend) < dustSats

bytesofman marked an inline comment as done.
bytesofman added inline comments.
cashtab/src/transactions/index.js
152 ↗(On Diff #48560)

It would be better to check

needsAnotherUtxo = inputSatoshis < satoshisToSend + feeForThisTx

However, we need to build the tx to get feeForThisTx.

There is an available optimization to build the tx and get the fee using dummy (i.e. unsigned) inputs. However I am not totally sure how to do this (or if it is practical to do given the many different input types soon possible in Cashtab). I am also not sure on the performance trade-offs of calculating the fee every time with dummy inputs vs "getting it right" > ~95% of the time by using this check, then occasionally having to build the tx twice (as in the edge case tests added in this diff).

And, anyway, Cashtab is already behaving this way -- so this diff is still an improvement on existing behavior.

e.g. change the loop to one that tracks the index and if index = spendableUtxos.length && needsAnotherUtxo then throw exception here.

We get this result though without having to perform this check on every single utxo. imo not good practice to always "check if it's the last one" if you don't have to do this, just put the condition at the end.

tbh yes, it is still a bit messy. We should probably add utxo selection to ecash-lib, as it is a weird problem for app developers to deal with. But I don't think we need to do this before getting NFT trading into Cashtab.

161 ↗(On Diff #48560)

satsPerKb in Cashtab is dynamic in this way -- but this is handled at the callsite. It's kept as a param in the function to support unit testing at different fee rates.

dustSats should be a param, yes, will handle this in a separate diff.

bytesofman added inline comments.
cashtab/src/transactions/index.js
152 ↗(On Diff #48560)

correction... if index = spendableUtxos.length && !needsAnotherUtxo && (inputSatoshis - satoshisToSend) < dustSats

imo we aren't really gaining anything by checking this on every single iteration when we get the same behavior with just continue

It is probably not the best interface with ecash-lib, as you mention, but I think the solution there is adding utxo selection to ecash-lib, and iterating through different types of txs with Cashtab is a good way to see what we really need that to look like (for example, the whole idea of "needing" some utxos in inputs that are special in some way -- token utxos or p2sh agora ads -- this is not something handled by legacy BTC utxo selection algorithms)

This revision is now accepted and ready to land.Jul 10 2024, 12:53