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

Receipt and Action validation is error prone and is missing compile-time checks #10397

Open
nagisa opened this issue Jan 9, 2024 · 0 comments
Labels
A-contract-runtime Area: contract compilation and execution, virtual machines, etc A-transaction-runtime Area: transaction runtime (transaction and receipts processing, state transition, etc) C-housekeeping Category: Refactoring, cleanups, code quality

Comments

@nagisa
Copy link
Collaborator

nagisa commented Jan 9, 2024

Today we have some data validation in various places of our system in the form of validate_receipt, validate_actions etc.

These functions are then invoked almost arbitrarily to check that the data types are indeed valid to work with further along the line.

The primary issue with how these validation are constructed is the fact that they take an already deserialized type they are supposed to validate (e.g. fn {validate_action} (.., &Action, ..) or fn {validate_receipt} (.., &Receipt, ..).) This poses a major flaw in the setup – both Actions and Receipts can exist in – and indeed be used by – any part of the code without the required verification steps being executed a-priori.

The other foot gun here is that producing these types can itself result in unexpected validation. Consider for example deserialization. If any of the types internal to Action or Receipt is “invalid”, the deserialization of these types can fail with a different error or in a different location, unintentionally causing a protocol change. Similarly there are parts of the codebase where the types stored have knowingly not been validated with an expectation that this is handled sometime in the future (e.g. DeleteAccountAction in near-vm-runner/src/logic/logic.rs,) however constructing those types in the first place requires to construct and store as fields types representing valid values!


I'm not quite sure how actionable this issue is really. In my mind the ideal setup would be to have multiple sets of types for each concept. For example Action:

/// Comes out of the database, network, contracts during execution, etc.
///
/// Stores literally a bag of bytes encoded in whatever format.
struct UnvalidatedAction(SerializedBagOfBytesOrWhatever);
/// Valid 
impl UnvalidatedAction {
    /// Deserialize an unvalidated action into its validated form.
    ///
    /// Implements any relevant checks (BEFORE constructing internal fields!)
    fn validate(&self) -> Action { ... }
}

/// Wherever this exists you know you can rely on this being valid.
enum Action { ... }

However it seems like it would be an extreme engineering challenge to actually achieve something like this. Primarily because implementing this would now require going through every affected data type and carefully vetting all the failure modes in every location they are constructed (which also means implicit locations as might be derived by deserialization types.)

Perhaps a slightly easier option would be to convert each of these types to an enum along the lines of

enum MaybeAction {
    Unverified(Action? SerializedBagOfBytes? Idk.)
    Verified(Action)
}

impl MaybeAction {
    fn unwrap(self) -> Action { let Self::Validated(a) = self else { panic!("boom!") }; a }
}

which then would at least check for these sorts of conditions at the runtime. We would still need to rewrite the validation code to operate on bytes rather than already-deserialized structures and to ensure that any errors previously emitted by deserialization continues being emitted in the same place… ARGH! Maybe just store the already deserialized type in either case…?

@nagisa nagisa added C-housekeeping Category: Refactoring, cleanups, code quality A-transaction-runtime Area: transaction runtime (transaction and receipts processing, state transition, etc) A-contract-runtime Area: contract compilation and execution, virtual machines, etc labels Jan 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-contract-runtime Area: contract compilation and execution, virtual machines, etc A-transaction-runtime Area: transaction runtime (transaction and receipts processing, state transition, etc) C-housekeeping Category: Refactoring, cleanups, code quality
Projects
None yet
Development

No branches or pull requests

1 participant