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

[RFC][libra-framework] Refactored prologue out of LibraAccount #5881

Closed
wants to merge 1 commit into from

Conversation

runtian-zhou
Copy link
Contributor

@runtian-zhou runtian-zhou commented Sep 2, 2020

Motivation

Moved all transaction related logic outside of LibraAccount and refactored prologue and epilogue logic into a single module.

For the LibraAccount module there will be two public apis: check_sender and charge_transaction, which is the LibraAccount related logic that is extracted out from the old prologue and epilogue.

Justification:

  1. All transaction prologue and epilogues will now be grouped into one module. Now Block Prologue is the only function that LibraVM invokes that is outside of this module.
  2. Cleaned up the bump_sequence_number ugliness in the writeset epilogue.
  3. Move all Libra transaction related logic outside of LibraAccount module.
  4. Maximize the code shared across different prologues and epilogues.

I haven't fixed the code in libra_vm invokation so tests are guarenteed to fail. Just want to know how people feel like about this rework. Will fixup tests if people are happy about this.

Have you read the Contributing Guidelines on pull requests?

Yes

Test Plan

Haven't tested yet.

@runtian-zhou runtian-zhou added the breaking Any changes that will require restarting the testnet label Sep 2, 2020
@bors-libra bors-libra added this to In Review in bors Sep 2, 2020
@runtian-zhou runtian-zhou marked this pull request as draft September 2, 2020 04:24
@@ -0,0 +1,213 @@
address 0x1 {

module LibraTransaction {
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 the newly created module for transaction prologues and epilogues (what used to be LibraWriteSetManager).

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we can come up with a name conveying that this has transaction processing business logic rather than defining the structure of transactions (e.g., there's no struct Transaction here). Maybe TransactionManager, TransactionLifecycle, TransactionProcessing?

sender: address,
/// This function can only be called during the epilogue as any invokation during the execution body will cause
/// the sequence number to be poisoned.
public fun charge_transaction<Token>(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that although this function is public, it is not invokable from regular user scripts as it will cause the sequence number to be incremented and thus fail the assertion at line 885.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand--can't the caller choose any value they want for txn_sequence_number? E.g.

fun my_evil_function(signer: &signer) {
  let any_num_i_want = 7;
  LibraAccount::epilogue(signer, signer, 0, any_num_i_want);
}

@MIRAI-bot
Copy link

[move-prover] Move Prover test (language/move-prover/cargo test) on commit 2028e60 did fail. See details.

@jolestar
Copy link
Contributor

jolestar commented Sep 3, 2020

It is possible to support a new type of visibility in Move, similar to java's protected or rust's pub(crate), where the methods that use this visibility can only be called by the modules at the same address.

@runtian-zhou
Copy link
Contributor Author

It is possible to support a new type of visibility in Move, similar to java's protected or rust's pub(crate), where the methods that use this visibility can only be called by the modules at the same address.

I'd really love for support like that. But IMHO we don't have a clear path towards it anytime soon as supporting such feature will actually need to change the bytecode format + bytecode verifier, which seems to be a bit heavy right now.

@bob-wilson
Copy link
Contributor

I really like this change, but I have reservations about doing this kind of refactoring so long after our feature-complete date. What about if we put all the prologue/epilogue code into LibraAccount (where most of it is already) instead of creating a new module? It wouldn't be as nicely factored but it seems like it would achieve most of the benefits with far less churn in the code.

@runtian-zhou
Copy link
Contributor Author

I really like this change, but I have reservations about doing this kind of refactoring so long after our feature-complete date. What about if we put all the prologue/epilogue code into LibraAccount (where most of it is already) instead of creating a new module? It wouldn't be as nicely factored but it seems like it would achieve most of the benefits with far less churn in the code.

I think the major concern is that we need to expose charge_transaction even if we keep the current structure of code. The only gain that buys us is that the LibraVM will remain unchanged.

@bob-wilson
Copy link
Contributor

I think the major concern is that we need to expose charge_transaction even if we keep the current structure of code. The only gain that buys us is that the LibraVM will remain unchanged.

Why? Wouldn't all the callers be in the LibraAccount module so that charge_transaction could be private?

/// This function can only be called during the epilogue as any invokation during the execution body will cause
/// the sequence number to be poisoned.
public fun charge_transaction<Token>(
vm_signer: &signer,
Copy link
Contributor

Choose a reason for hiding this comment

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

unused? Did you mean to assert Signer::address_of(vm_signer) == VM_RESERVED_ADDRESS?

sender: address,
/// This function can only be called during the epilogue as any invokation during the execution body will cause
/// the sequence number to be poisoned.
public fun charge_transaction<Token>(
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand--can't the caller choose any value they want for txn_sequence_number? E.g.

fun my_evil_function(signer: &signer) {
  let any_num_i_want = 7;
  LibraAccount::epilogue(signer, signer, 0, any_num_i_want);
}

) {
assert(
LibraTransactionPublishingOption::is_module_allowed(sender),
PROLOGUE_EMODULE_NOT_ALLOWED
Copy link
Contributor

Choose a reason for hiding this comment

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

Did we decide to use Errors here as well? @tnowacki @tzakian

Copy link
Contributor

Choose a reason for hiding this comment

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

#5876

I have a PR up to use Errors everywhere. We should probably get that in first


/// The common prologue is invoked at the beginning of every transaction
/// It verifies:
/// - The account's auth key matches the transaction's public key
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a comment about chain_id?

/// It verifies:
/// - The account's auth key matches the transaction's public key
/// - That the account has enough balance to pay for all of the gas
/// - That the sequence number matches the transaction's sequence key
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
/// - That the sequence number matches the transaction's sequence key
/// - That the account's sequence number matches the transaction's sequence number

assert(sender == CoreAddresses::LIBRA_ROOT_ADDRESS(), PROLOGUE_EINVALID_WRITESET_SENDER);

// Currency code don't matter here as it won't be charged anyway.
LibraAccount::check_sender<LBR::LBR>(account, 0, writeset_sequence_number, writeset_public_key);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we call prologue_common here instead of this to resolve the TODO below?

) {
assert(sender == CoreAddresses::LIBRA_ROOT_ADDRESS(), PROLOGUE_EINVALID_WRITESET_SENDER);

// Currency code don't matter here as it won't be charged anyway.
Copy link
Contributor

Choose a reason for hiding this comment

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

This + the 0 gas price feels a bit icky and is making me wonder whether we want separate LibraAccount::gas_prologue and LibraAccount::replay_prevention_prologues (or similar...)

// Charge for gas
assert(txn_max_gas_units >= gas_units_remaining, Errors::invalid_argument(EGAS));
let gas_used = txn_max_gas_units - gas_units_remaining;
assert((txn_gas_price as u128) * (gas_used as u128) <= MAX_U64, Errors::limit_exceeded(EGAS));
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe do assert(txn_gas_price <= MAX_U64 / gas_used) to avoid the u128 conversions?

) {
let sender = Signer::address_of(account);

// Charge for gas
Copy link
Contributor

Choose a reason for hiding this comment

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

I find this comment confusing given that the actual charging happens in LibraAccount. This part is just sanity-checking the gas price and gas unit values.


/// The failure_epilogue is invoked at the end of transactions when the transaction is aborted during execution or
/// during `success_epilogue`.
fun failure_epilogue<Token>(
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed with you that these are now the same and we can kill one of them.

@tnowacki
Copy link
Contributor

tnowacki commented Sep 8, 2020

Also very worried about making a change like this so late.

Why? Wouldn't all the callers be in the LibraAccount module so that charge_transaction could be private?

Do we have an answer on this? I think that if we don't need to expose new public APIs, we shouldn't do it. Even if it means more code duplication.
In short, I don't really yet buy many of the motivations outlined in the summary, but could be convinced otherwise

Additionally, we should really re-evaluate this after #5876. There would only need to be a single epilogue in LibraAccount and might help us look at things differently

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking Any changes that will require restarting the testnet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants