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

Refactor mempool tests #2771

Merged
merged 10 commits into from
Sep 23, 2021
Merged

Conversation

jvff
Copy link
Contributor

@jvff jvff commented Sep 17, 2021

Motivation

I was working on tests for #2682, and I saw an opportunity to tweak unmined_transactions_in_blocks a bit, but ended up getting carried away and refactored some other parts as well. I'm not sure if these are good ideas, so any feedback is welcome.

Solution

The biggest change is that unmined_transactions_in_blocks receives a range of block heights instead of a maximum height, and returns an iterator instead of a tuple with a Vec and the transaction count. The code that use this function were also tweaked a bit in an attempt to make clearer how the unmined transactions were being used and to remove some redundancy.

Review

Reviewer Checklist

  • Code implements Specs and Designs
  • Tests for Expected Behaviour
  • Tests for Errors

@jvff jvff requested a review from dconnolly September 17, 2021 01:17
@zfnd-bot zfnd-bot bot assigned jvff Sep 17, 2021
@conradoplg
Copy link
Collaborator

In #2770 I created a test that had to split the tx list in three parts (the first, the rest except the last, the last, and a reference to the second-to-last). I tried to come up with a non "C-like" way that did not uses indices everywhere, but it relied on some Vec methods (pop()). Here you also had to convert it back to Vec, so maybe unmined_transactions_in_blocks could be reverted to return Vec again, but leaving the other improvements? (Not really sure if that would be better)

An attempt to improve readability a bit by not returning a tuple with a
value that can be obtained from a single return type.
Use a more functional style to try to make it a bit clearer.
Allow a finer control over the block range to extract the transactions
from.
It was previously not clear that only the first genesis transaction was
being used. The remaining transactions in the genesis block were
discarded and then readded later.
Remove a redundant internal `ready_and` call.
Remove unnecessary deserializations and heap allocations.
Make the separation between the transactions expected to be in the
mempool and those expected to be rejected clearer.
Allow getting the last transaction more easily.
@jvff
Copy link
Contributor Author

jvff commented Sep 21, 2021

In #2770 I created a test that had to split the tx list in three parts (the first, the rest except the last, the last, and a reference to the second-to-last). I tried to come up with a non "C-like" way that did not uses indices everywhere, but it relied on some Vec methods (pop()). Here you also had to convert it back to Vec, so maybe unmined_transactions_in_blocks could be reverted to return Vec again, but leaving the other improvements? (Not really sure if that would be better)

Interesting problem! Regarding keeping the return type as Vec, I think that's not necessary, you can call .collect() after the function call and get the same result. But if you want to do it without using Vec methods, my first thought would be to try something like:

let mut transactions = unmined_transactions_in_blocks(height, network);
let first_transaction = transactions.next().unwrap();
let last_transaction = transactions.next_back().unwrap();

// Then use the "middle" transactions in `transactions`

Another option for when the iterator is not a DoubleEndedIterator is:

let mut transactions = unmined_transactions_in_blocks(height, network);
let first_transactions = transactions.next().unwrap();
let mut transactions: Vec<_> = transactions.collect();
let last_transaction = transactions.pop().unwrap();

I updated the PR to return a DoubleEndedIterator. I hope this helps 😅

@conradoplg
Copy link
Collaborator

I updated the PR to return a DoubleEndedIterator. I hope this helps

That's great, the best of both worlds 😁
I'm glad with this, let's wait for @oxarbitrage to take a look

@teor2345
Copy link
Contributor

I don't mind what you do here - whatever works best for writing tests.

But I want to encourage you to merge this refactor soon, so it doesn't block other work, or cause rework.

@jvff jvff marked this pull request as ready for review September 23, 2021 11:15
@oxarbitrage oxarbitrage self-requested a review September 23, 2021 11:30
Copy link
Contributor

@oxarbitrage oxarbitrage left a comment

Choose a reason for hiding this comment

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

This changes are good to me.

@conradoplg conradoplg enabled auto-merge (squash) September 23, 2021 13:18
@conradoplg conradoplg merged commit 11b77af into ZcashFoundation:main Sep 23, 2021

// Insert them all to the storage
for unmined_transaction in unmined_transactions.clone() {
storage.insert(unmined_transaction)?;
}

// Separate transactions into the ones expected to be in the mempool and those expected to be
// rejected.
let rejected_transaction_count = total_transactions - MEMPOOL_SIZE;
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

// Separate transactions into the ones expected to be in the mempool and those expected to be
// rejected.
let rejected_transaction_count = total_transactions - MEMPOOL_SIZE;
let expected_to_be_rejected = &unmined_transactions[..rejected_transaction_count];
Copy link
Contributor

Choose a reason for hiding this comment

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

This will break when we implement ZIP-401

Copy link
Contributor Author

@jvff jvff Sep 23, 2021

Choose a reason for hiding this comment

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

In this case it might make sense to change the assertions to be:

  1. Does the storage contain exactly MEMPOOL_SIZE transactions?
  2. Are all transactions in the mempool storage from the unmined_transactions list?
  3. Are all transaction IDs in the mempool from the unmined_transactions list (maybe minus the "accepted" transactions)?
  4. (Maybe) Are they all unique?

Playing with an idea here:

let mut remaining_transactions: HashMap<UnminedTxId> = unmined_transactions
    .into_iter()
    .map(|transaction| transaction.id)
    .collect();

let stored_transactions = storage.transactions();

assert_eq!(stored_transactions.len(), MEMPOOL_SIZE));

for transaction in stored_transactions {
    assert!(remaining_transactions.remove(&transaction.id));
}

let rejected_transactions = storage.rejected_transactions().into_iter().collect();

assert_eq!(remaining_transactions, rejected_transactions);

@jvff jvff deleted the refactor-mempool-tests branch September 24, 2021 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants