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

feat: refactor key-related field operations to be atomic #5178

Merged

Conversation

AaronFeickert
Copy link
Collaborator

@AaronFeickert AaronFeickert commented Feb 13, 2023

Description

Refactors key-related database field operations to be atomic.

Closes issue 5177.

Motivation and Context

Key-related database fields always travel together. We need a consistent set of secondary key version identifier, secondary key salt, and encrypted main key in order to set up the XChaCha20-Poly1305 cipher used for database encryption operations and passphrase changes.

While a recent PR ensures that write operations for these fields are done atomically via a write transaction, there is no corresponding read transaction. It's therefore possible that those fields are inconsistent. While this should only result in an error and require the user to load their wallet again, it seemed like a smart idea to ensure that reads are consistent for any future use cases.

This PR refactors the handling of those fields to reduce redundancy and ensure atomicity for reads and writes. It introduces a new DatabaseKeyFields struct that handles reads and writes, and additionally takes care of encoding and decoding of the underlying data.

It also makes the handling of the three fields more consistent. Previously, individual reads and writes required the use of a complex match to handle different states. This functionality has been mostly moved into DatabaseKeyFields to make these states more apparent.

How Has This Been Tested?

Existing unit tests pass. Manually tested the following operations:

  • setting up a new wallet and successfully loading it with the correct passphrase
  • setting up a new wallet and unsuccessfully loading it with an incorrect passphrase
  • setting up a new wallet and unsuccessfully loading it due to a simulated read transaction failure
  • failing to set up a new wallet due to a simulated write transaction failure
  • failing to set up a new wallet due to a simulated read transaction failure
  • a successful passphrase change via CLI
  • an unsuccessful passphrase change via CLI due to an incorrect existing passphrase
  • an unsuccessful passphrase change via CLI due to a mismatched new passphrase
  • an unsuccessful passphrase change via CLI due to a simulated write transaction failure
  • an unsuccessful passphrase change via CLI due to a simulated read transaction failure

It does not seem possible to directly test read operation inconsistency caused by a simultaneous write operation.

@AaronFeickert AaronFeickert marked this pull request as ready for review February 13, 2023 17:53
@AaronFeickert
Copy link
Collaborator Author

AaronFeickert commented Feb 13, 2023

As before, you can simulate a read or write transaction failure by having the corresponding closure return an Error::RollbackTransaction error.

SWvheerden
SWvheerden previously approved these changes Feb 14, 2023
Copy link
Collaborator

@SWvheerden SWvheerden left a comment

Choose a reason for hiding this comment

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

utACK

base_layer/wallet/src/storage/sqlite_db/wallet.rs Outdated Show resolved Hide resolved
hansieodendaal
hansieodendaal previously approved these changes Feb 14, 2023
Copy link
Contributor

@hansieodendaal hansieodendaal left a comment

Choose a reason for hiding this comment

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

utaCK

base_layer/wallet/src/storage/sqlite_db/wallet.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@stringhandler stringhandler left a comment

Choose a reason for hiding this comment

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

Happy to merge after the map_errs are removed

connection
.transaction::<_, Error, _>(|| {
secondary_key_version = WalletSettingSql::get(&DbKey::SecondaryKeyVersion, connection)
.map_err(|_| Error::RollbackTransaction)?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This mapping is not actually needed. Any Err will rollback the transaction. The docs on diesel.rs don't reflect this in v1.4, but have been updated in the 2.0 docs on docs.rs. In either case, I checked the source and it rolls back on Err.

So it's better to just return the error with ? and have the original error find their way to the logs

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I had tried this initially, but the compiler complained that the internal error couldn't be converted to a diesel error.

.transaction::<_, Error, _>(|| {
WalletSettingSql::new(DbKey::SecondaryKeyVersion, self.secondary_key_version.to_string())
.set(connection)
.map_err(|_| Error::RollbackTransaction)?;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment here

@stringhandler stringhandler merged commit 1ad79c9 into tari-project:development Feb 14, 2023
@AaronFeickert AaronFeickert deleted the refactor-key-fields branch February 14, 2023 16: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.

Database reads on key-related fields should be atomic
4 participants