Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/coin control ordering #15597

Merged
merged 3 commits into from
Dec 18, 2024
Merged

Feat/coin control ordering #15597

merged 3 commits into from
Dec 18, 2024

Conversation

komret
Copy link
Contributor

@komret komret commented Nov 27, 2024

Description

I tried to bear performance in mind.

Related Issue

Resolve #14545

Screenshots:

Screenshot 2024-12-02 at 18 50 56
Screenshot 2024-12-06 at 18 05 09

Copy link

github-actions bot commented Nov 27, 2024

🚀 Expo preview is ready!

  • Project → trezor-suite-preview
  • Platforms → android, ios
  • Scheme → trezorsuitelite
  • Runtime Version → 21
  • More info

Learn more about 𝝠 Expo Github Action

@komret komret force-pushed the feat/coin-control-ordering branch 2 times, most recently from 5640b00 to 804e338 Compare December 2, 2024 17:49
@komret komret marked this pull request as ready for review December 2, 2024 17:52
}

const sortFromNewestToOldest = (a: AccountUtxo, b: AccountUtxo) => {
let valueA: number;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to get rid of the let by doing something like this:

const sortFromNewestToOldest: UtxoSortingFunction =
    ({ accountTransactions }) =>
    (a: AccountUtxo, b: AccountUtxo) => {
        if (a.blockHeight > 0 && b.blockHeight > 0) {
            return b.blockHeight - a.blockHeight;
        }

        // Pending transactions do not have blockHeight, so we must use blockTime of the transaction instead.
        const getBlockTime = (txid: string) => {
            const transaction = accountTransactions.find(transaction => transaction.txid === txid);

            return transaction?.blockTime ?? 0;
        };

        return new BigNumber(getBlockTime(b.txid)).comparedTo(new BigNumber(getBlockTime(a.txid)));
    };

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the lets there because it just seems easier with the secondary sorting.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please reconsider if you decide to refactor secondary sorting by wrapping it from outside.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peter-sanderson
Copy link
Contributor

peter-sanderson commented Dec 3, 2024

I have tested it and it works. Good job!

However I would like to see tests for the sorting function especially the complicated on (largest/newest) + some code polishing.

@komret komret force-pushed the feat/coin-control-ordering branch 2 times, most recently from d3a5f3e to 1d0258e Compare December 6, 2024 16:55
@komret
Copy link
Contributor Author

komret commented Dec 6, 2024

However I would like to see tests for the sorting function especially the complicated on (largest/newest) + some code polishing.

Thanks, I shamelessly stole your commits. Apologies for not using fixups :( I only made changes to the third commit, though.

The changes I made:

  • move sorting functions to a separate file
  • implement secondary and tertiary sorting by txid and vout
  • add unit tests

@komret komret force-pushed the feat/coin-control-ordering branch from 1d0258e to 587e8d1 Compare December 6, 2024 17:02
];

describe('sortUtxos', () => {
it('should sort UTXOs by newest first', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test description should not start with should. I believe we even have it in Notion somewhere (but I am lazy to search it).

Suggested change
it('should sort UTXOs by newest first', () => {
it('sorts UTXOs by newest first', () => {

expect(sortedUtxos).toEqual([UTXOS[0], UTXOS[2], UTXOS[1], UTXOS[3]]);
});

it('should sort UTXOs by largest first', () => {
Copy link
Contributor

@peter-sanderson peter-sanderson Dec 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The (unit) test shall test only one thing. And the assertion shall be done in a way, it is clearly visible what is important (the amount of transaction). This way to work with test I have to go back and forth to see what hides behind UTXOS[1]

This is what I believe is much more readable, and maintainable:

    it('sorts by size, largest first', () => {
        const utxos = [
            testMocks.getUtxo({ amount: '1', txid: 'txid1' }),
            testMocks.getUtxo({ amount: '2', txid: 'txid2' }),
        ];
        const sortedUtxos = sortUtxos(utxos, 'largestFirst', ACCOUNT_TRANSACTIONS);
        expect(sortedUtxos.map(it => it.amount)).toEqual(['2', '1']);
    });

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I like it, but I think this is more beneficial in the expect clause than the input where some lines can be saved: 82fc888

Copy link
Contributor

@peter-sanderson peter-sanderson Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I believe that being explicit in tests and make it clear what goes in and out is more important then saving some lines.

For example: UTXOS.slice(1, 4) gives me no insight what is the given state of the test.

I think all tests shall follow the pattern:

  • given (input of the test)
  • when (what is tested)
  • then (assert of the output)

All technical details can be hidden and abstracted, but I think the input (what is tested case), the test subject (what piece is tested), and result (what is expected result) shall be visible on the very first sight.


To saving some lines maybe this can be shorted:

        const utxos = [
            testMocks.getUtxo({ amount: '2', txid: 'txid2' }),
            testMocks.getUtxo({ amount: '2', txid: 'txid1', vout: 1 }),
            testMocks.getUtxo({ amount: '2', txid: 'txid1', vout: 0 }),
        ];

to something like:

const utxos = [utxo_amount2_tx2_vout1, utxo_amount2_tx1_vout1, , utxo_amount2_tx1_vout0]

But I think its marginal.


Anyway, this is not the hill to die on, but I just wanted to explain my reasoning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, your explanation makes sense. I'd prefer not to change it here now that it is done, but I'll bear it in mind next time.

Copy link
Contributor

@peter-sanderson peter-sanderson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here is another suggestion aa0c473

It is a draft, feel free to use it, or not. Up to you.

Espetially tests for newestFirst and oldestFirst are bit messy. But I wanted to show the principles.

@komret
Copy link
Contributor Author

komret commented Dec 16, 2024

@peter-sanderson please check 82fc888. If you approve, I will squash it all to the third commit.

]);
});

it('should return the original array if utxoSorting is undefined', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it('should return the original array if utxoSorting is undefined', () => {
it('returns the original array if utxoSorting is undefined', () => {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Contributor

@peter-sanderson peter-sanderson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 GJ! 🚀

@komret komret force-pushed the feat/coin-control-ordering branch from 82fc888 to c1c198b Compare December 18, 2024 13:50
@komret komret force-pushed the feat/coin-control-ordering branch from c1c198b to 49f7088 Compare December 18, 2024 13:51
@komret komret merged commit 280c7d1 into develop Dec 18, 2024
29 of 30 checks passed
@komret komret deleted the feat/coin-control-ordering branch December 18, 2024 14:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Coin Control: UTXO sorting
2 participants