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

Can verify() storage reads be trusted? #2447

Open
igormcoelho opened this issue Apr 22, 2021 · 3 comments
Open

Can verify() storage reads be trusted? #2447

igormcoelho opened this issue Apr 22, 2021 · 3 comments
Labels
Question Used in questions

Comments

@igormcoelho
Copy link
Contributor

igormcoelho commented Apr 22, 2021

A recent discussion on neo-project/proposals#137 got me thinking: Can verify() storage reads be trusted?

I'll put a simple example.

  • Two transactions to the same ContractY are put on same block, TxA then TxB.
  • ContractY has 1 GAS
  • TxA extracts 0.5 GAS to fund its operation, via CheckWitness against its "owner"
  • TxA changes "owner" during invocation
  • TxB extracts 0.5 GAS to fund its operation, via CheckWitness against its "owner"
  • TxB fails to change "owner" during invocation, since it has already been changed
bool verify() {
    return CheckWitness(Storage.Get("this_contract_owner"));
} 

void execute() {
    if(!CheckWitness(Storage.Get("this_contract_owner")))
         return;
    Storage.Put("this_contract_owner", "another_owner");
}

Am I really mistaken here, or Neo System will actually (and correctly) extract GAS fees and fund both operations, but on the other hand, ContractY will have allowed spending GAS from its account when its "owner" has already been changed?

Sorry for re-discussing things, we have discussed already many times the execution order of transactions and the precise points where re-verification happens, but as long as no re-verification happens between tx executions on block, I feel that ContractY will pay for something it shouldn't.

@igormcoelho igormcoelho added the Question Used in questions label Apr 22, 2021
@igormcoelho
Copy link
Contributor Author

@roman-khimov can you help me?

@roman-khimov
Copy link
Contributor

The behavior you've described is what will happen with current implementation, that's true.

as long as no re-verification happens between tx executions on block, I feel that ContractY will pay for something it shouldn't.

The problem is that TxB is already in a block by that time, so its fees have to be paid and if ContractY is the sender it'll pay, there is no way around it.

We do have in-block transaction reverification in NeoGo, but it does it the same way regular verification happens --- independently for each transaction before block processing (we can't do it in other way without breaking compatibility). And it's mostly a safety check in that we'll just refuse a block with invalid transaction.

Fixing your case actually requires not just reverification, but running/reverifying transactions before signing a block, so that we could exclude TxB from new block completely. Which certainly can be done, but it's a substantial change, so I doubt it's something for RC phase.

@igormcoelho
Copy link
Contributor Author

igormcoelho commented Apr 24, 2021

Thanks for the clarifications @roman-khimov . In fact, I'm now a big defender of a single-verification system, and I recognize that Neo System needs to be paid anyway, otherwise it may suffer attacks. So I wouldn't defend (anymore) more flexibility during verification phase, I think this is decided already (a good decision). Neo will certainly get its GAS, and that's good.
However, from the perspective of someone using verify() to make operations, and perhaps very costly operations (on GAS), it could be impractical to leak GAS on unexpected operations. In fact, as long as the context you operate verify() and that you execute is not the same, so on general you cannot really know what you're doing during verification phase, regarding storage reads, that's my conclusion on it. I know that for specific contracts you can do it, specially if you are able to parse and control the invocation script, thus escaping most non-determinisms.
On the other hand, regarding assets, NEP-TTT is able to provide exactly the same functionality as GAS, avoiding non-determinism even with basic storage reads... but that's possible just because the use-case is very constrained, in the scope of tokens with additive properties (that allows safely paying operations), specially by providing finalizeTransfer that runs after contract has been executed (or failed), so it could flag users that are using GAS for FAILED transactions, that is another big problem for sponsorship.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question Used in questions
Projects
None yet
Development

No branches or pull requests

2 participants