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

Wallet Management Remaining Endpoints: update & update passphrase #249

Merged
merged 9 commits into from
May 10, 2019

Conversation

KtorZ
Copy link
Member

@KtorZ KtorZ commented May 9, 2019

Issue Number

#220

Overview

  • I have implemented API handlers & wallet layer methods for updating wallet metadata and updating wallet passphrase
  • I have altered the API specification to remove 'required' for passphrase info on wallets. In practice, this can only happen now with very tight concurrent calls, but, it conveys the idea that wallet could exist without passphrase (which we'll come up to soon enough when re-introducing externally owned wallets).

Comments

@KtorZ KtorZ requested a review from akegalj May 9, 2019 12:31
@KtorZ KtorZ self-assigned this May 9, 2019
@KtorZ KtorZ mentioned this pull request May 9, 2019
9 tasks
Copy link
Contributor

@rvl rvl left a comment

Choose a reason for hiding this comment

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

Sweet - questions/comments below...

@@ -144,8 +148,8 @@ newtype WalletPutData = WalletPutData
} deriving (Eq, Generic, Show)

data WalletPutPassphraseData = WalletPutPassphraseData
{ oldPassphrase :: !(ApiT (Passphrase "encryption"))
, newPassphrase :: !(ApiT (Passphrase "encryption"))
{ oldPassphrase :: !(ApiT (Passphrase "encryption-old"))
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these -old/-new types really needed?

Copy link
Member Author

Choose a reason for hiding this comment

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

No, this has purely a self-documenting purpose. Like most of our phantom types actually.

-> (Passphrase "encryption-old", Passphrase "encryption-new")
-> Property
walletUpdatePassphraseWrong wallet (xprv, pwd) (old, new) =
pwd /= coerce old ==> monadicIO $ liftIO $ do
Copy link
Contributor

Choose a reason for hiding this comment

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

Could the precondition be changed to be an assertion instead? i.e.

attempt `shouldBe` if pwd == coerce old then Left err else Right ()

Same goes for the other properties with preconditions.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmmm, could be. But I'd rather have two properties with two opposite pre-conditions than a single one with a variable evaluation. It makes things clearer when a test fail, what exactly is failing.

@@ -461,7 +461,6 @@ definitions:
- balance
- delegation
- name
- passphrase
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a question. Does this mean the swagger spec says that's it ok to create a wallet without a passphrase?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's even stronger than that. It means that ultimately, it's okay to create a wallet without a root private key. We'll come to that soonish.

@KtorZ KtorZ force-pushed the KtorZ/220/remaining-endpoints branch from e666879 to ae7f1e1 Compare May 10, 2019 09:23
meta <- _readWalletMeta wid
DB.putWalletMeta db (PrimaryKey wid) (modify meta)

, updateWalletPassphrase = \wid (old, new) -> do
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 what happens if there are two requests at the same time in a rece condition event both calling updateWalletPassphrase

Copy link
Member Author

Choose a reason for hiding this comment

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

There's a DB lock, so ultimately, they'll be sequentialized.

-> ExceptT ErrNoSuchWallet IO ()
_attachPrivateKey wid (xprv, pwd) = do
hpwd <- liftIO $ encryptPassphrase pwd
DB.putPrivateKey db (PrimaryKey wid) (xprv, hpwd)
Copy link
Contributor

Choose a reason for hiding this comment

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

could this fail due to the race condition?

Copy link
Member Author

Choose a reason for hiding this comment

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

If there's no wallet? Yes, it would fail with ErrNoSuchWallet. Could happen if the wallet is deleted before the key gets added. That's okay and already handled.

Copy link
Contributor

Choose a reason for hiding this comment

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

MVar implementation shouldn't. I hope sql version won't as well

@KtorZ KtorZ force-pushed the KtorZ/220/remaining-endpoints branch from ae7f1e1 to b6c72b4 Compare May 10, 2019 13:17
Copy link
Contributor

@akegalj akegalj left a comment

Choose a reason for hiding this comment

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

I have few remarks on race condition. Don't have much experience there but it might be worth a look

return ()
Just (ApiT wName) ->
liftHandler $ W.updateWallet w wid (\meta -> meta { name = wName })
getWallet w (ApiT wid)
Copy link
Contributor

Choose a reason for hiding this comment

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

hm - could we have race condition between L178 and L179?

  • on line L178 wallet gets updated
  • another request is made which changes content of the same wid to <something_else>
  • on line L179 we return <something_else> which is not expected

Copy link
Contributor

Choose a reason for hiding this comment

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

In more details:

  • thread A wants to update a wallet to "Foo" but is stoped just before L179
  • thread B runs and updates a wallet. It changes name to "Bar"
  • thread A is resumed and it runs L179. As a result it returns "Bar" (but what A expects is tu return "Foo")

Copy link
Contributor

@akegalj akegalj May 10, 2019

Choose a reason for hiding this comment

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

ps - its no big deal for wallet name - and in fact request for updating a wallet is not something that will likely be at a race condition situation a lot (I don't see a reason why multiple users would need to change wallet name)

Copy link
Member Author

Choose a reason for hiding this comment

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

hm - could we have race condition between L178 and L179?

Yes. Could be. Though it doesn't matter much. I mean, the response will be slightly off in the case of concurrent calls. I don't think it's really important for things like metadata as long as, the internal DB state is updated correctly (which is the case with the lock mechanism).

@@ -7,6 +7,7 @@
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RoleAnnotations #-}
Copy link
Contributor

Choose a reason for hiding this comment

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

hm - never used this before

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe I'll bring the topic on next week but, we could make more use of coerce and Coercible from the base package. This allows for coercing between newtypes that have a same (or compatible) underlying representation. So instead of paying the cost of unwrapping and then re-wrapping a newtype, we can just call coerce which has no runtime cost as everything is done at compile-time; it's like a special primitive from GHC / base.

In order to do that, GHC needs some extra role annotation to know whether things can be coerced safely or not.

@KtorZ KtorZ force-pushed the KtorZ/220/remaining-endpoints branch from b6c72b4 to 9b635c5 Compare May 10, 2019 13:43
@KtorZ KtorZ merged commit d573bae into master May 10, 2019
@KtorZ KtorZ deleted the KtorZ/220/remaining-endpoints branch May 10, 2019 14:54
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