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: Receipt handling to be done in VM logic #6468

Merged
merged 22 commits into from Apr 5, 2022

Conversation

austinabell
Copy link
Contributor

@austinabell austinabell commented Mar 21, 2022

Refactors receipt handling from being done in all External implementations to being done in vm-logic. A few refactoring patterns were explored but this one solved the main issues of testability and deduplication the best I've found.

The tests are somewhat of a property-based test with all common cases included, but this can be expanded to be fuzzed as well. (might do this in separate PR though)

Copy link
Contributor

@matklad matklad left a comment

Choose a reason for hiding this comment

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

This looks beautiful!

I have only one overall comment -- let's just make receipts a public field of VMOutcome (burdening the caller with the task of converting them to actual recipts). Other than that, it looks basically good to me! Though, I didn't do a super thorough review, as this is still a draft.

runtime/near-vm-logic/src/logic.rs Outdated Show resolved Hide resolved
@austinabell austinabell changed the title WIP: Refactoring receipt handling to be done in VM logic refactor: Receipt handling to be done in VM logic Apr 1, 2022
@austinabell austinabell marked this pull request as ready for review April 1, 2022 15:04
@austinabell austinabell requested a review from mm-near April 4, 2022 13:51
@austinabell
Copy link
Contributor Author

@mm-near requesting review based on your review of #6285. Not sure who else to request for this with the new process.

I've avoided using specific gas amounts to make the tests less brittle, so let me know if you think this strategy is sufficient.

@matklad matklad self-requested a review April 4, 2022 14:08
runtime/near-vm-logic/src/gas_counter.rs Outdated Show resolved Hide resolved
runtime/near-vm-logic/src/tests/gas_counter.rs Outdated Show resolved Hide resolved
.iter()
.map(|(_, weight)| weight)
// Saturating sum of weights
.fold(0, |sum, val| sum.checked_add(*val).unwrap_or(u64::MAX));
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be just sum (ie, panic on overflow)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The implementation doesn't panic though, so this would cause inconsistency (one of the test cases has larger than u64::MAX ratio sum)

runtime/near-vm-logic/src/receipt_manager.rs Outdated Show resolved Hide resolved
runtime/near-vm-logic/src/receipt_manager.rs Outdated Show resolved Hide resolved
Comment on lines 456 to 459
if let Some(Action::FunctionCall(FunctionCallAction { ref mut gas, .. })) = self
.action_receipts
.0
.get_mut(*receipt_index)
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's extract this bit into fn get_fuction_call_action_mut function, it'll more clearl y explain the purpose of FunctionCallIndex construct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason I didn't do this is because this closure is used within code blocks where self is borrowed immutably, so this wouldn't be able to be used. I'll extract it into a helper function but I'm unsure that's better

runtime/near-vm-logic/src/receipt_manager.rs Outdated Show resolved Hide resolved
runtime/near-vm-logic/src/receipt_manager.rs Outdated Show resolved Hide resolved
Comment on lines 472 to 486
let mut distributed = 0;
for (action_index, GasWeight(weight)) in &self.gas_weights {
// This can't overflow because the gas_per_weight is floor division
// of the weight sum.
let assigned_gas = gas_per_weight * weight;

distribute_gas(action_index, assigned_gas);

distributed += assigned_gas
}

// Distribute remaining gas to final action.
if let Some((last_idx, _)) = self.gas_weights.last() {
distribute_gas(last_idx, gas - distributed);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 I am still not happy about this algorithm. A bad example here would be if we want to distribute 10 gas across 10 function calls evenly, but where each weight is 1000. In that situation, gas_per_weight will be zero, and only the last call will get the gas.

I suggest tackling this separately though, let's first land this much needed refactor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So with this, this is more of an edge case where the weight is greater or close to the amount of gas remaining.

The downside to this is just that it requires extra logic and computation to handle for this edge case. I'm also wondering in the case of splitting 12 gas among the 10 function calls, do the last two functions receive an extra 1 gas or how will this happen?

I don't have an opinion on which direction this goes, I was just defaulting to this for simplicity rather than creating a bunch of semantics that only does something subjectively meaningful with improper usage.

Copy link
Contributor

@matklad matklad Apr 4, 2022

Choose a reason for hiding this comment

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

I don't think handlig this particular cases requires that much excar logic. I think what we need here is

let assigned_gas = ((gas_to_distribute as u128 * weight as u128) / sum_weights) as u64;

That is, we just needlessly loosing precision here.

Distributing the remainder evenly is a different, smaller problem, that I think would require more complex logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is what I'm referring to, the remainder distribution being more calculations.

My intuition is that it would divide the amount of remaining gas by the number of individual weights to get the distribution of gas per function call, then get the remainder of that division (x) to attach the remaining gas to final x number of function calls.

Seems like unnecessary logic and computation for what will be trivial amounts of gas unless specifically misused by specifying gas weights larger than the amount of gas, but I'm happy to make the change if there is consensus on this :)

Copy link
Member

Choose a reason for hiding this comment

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

From what I see, here we choose between

1) let assigned_gas = ((gas_to_distribute as u128 * weight as u128) / sum_weights) as u64;
2) let assigned_gas = (gas_to_distribute / sum_weights) * weight;

I don't see a significant difference for regular usecases, so I would also choose (2) because it allows to make less operations.

runtime/near-vm-logic/src/tests/gas_counter.rs Outdated Show resolved Hide resolved
@austinabell austinabell requested a review from matklad April 5, 2022 01:40
@matklad
Copy link
Contributor

matklad commented Apr 5, 2022

This looks good to me! As this is a larger refactor, I think it'll benefit from a second pair of eyes doing the review as well.

@Longarithm would you have time to take a look here?

@matklad matklad requested a review from Longarithm April 5, 2022 08:21
Copy link
Member

@Longarithm Longarithm left a comment

Choose a reason for hiding this comment

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

LGTM, it's great that we are able to shorten the list of RuntimeExt fields.
However, I didn't look into all details, because the change touches many places in the code.

@matklad matklad requested a review from a team as a code owner April 5, 2022 22:03
@near-bulldozer near-bulldozer bot merged commit ab3e7f8 into master Apr 5, 2022
@near-bulldozer near-bulldozer bot deleted the austin/receipt_refactoring branch April 5, 2022 22:12
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.

None yet

3 participants