-
Notifications
You must be signed in to change notification settings - Fork 631
[CBR-401] For the validate-address API endpoint, add an IsOurs field … #3646
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good start @uroboros , thanks!
I have posted a bunch of comments which didn't occur to me when we first discussed the plan of action.
Furthermore, I would like to get @KtorZ 's opinion on this, as this touches the V1 API.
Last but not least, can I also ask you to modify the CHANGELOG.md to include this important API breaking change? I think this is exactly the kind of things we should track in the changelog, hehe.
Apart from that, looks good!
& paths %~ (DELETE, "/api/internal/reset-wallet-state") `setDescription` resetWalletStateDescription | ||
& paths %~ (POST, "/api/v1/transactions/fees") `setDescription` estimateFeesDescription | ||
& paths %~ (POST, "/api/v1/transactions/fees") `setDescription` estimateFeesDescription | ||
& paths %~ (GET, "/api/v1/addresses") `setDescription` getAddressDescription |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uhm, thorny -- I guess it's a question more for @KtorZ : how resilient is our magic combinator when it comes to "ambiguous" endpoints? Here /api/v1/addresses
might refer both to the endpoint "give me all the addresses" but also to /api/v1/addresses/{addressId}
which means "give me just this single address".
I have the suspect the combinator will try to apply this description to both endpoint, which is obviously not correct. Am I right? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here /api/v1/addresses might refer both to the endpoint "give me all the addresses" but also to /api/v1/addresses/{addressId} which means "give me just this single address"
Na. GET /api/v1/addresses
unambiguously refers to the list addresses endpoint. The single get would be referred to as: GET /api/v1/addresses/{addressId}
! So no issue here, that's resilient 💪
(as shown in the doc example: https://github.com/input-output-hk/cardano-sl/blob/develop/wallet-new/src/Cardano/Wallet/API/V1/Swagger.hs#L157-L161 )
-- In other words, `AddressNotRecognised` makes an inconclusive statement about | ||
-- an address, whereas `AddressIsOurs` is unambiguous. | ||
data AddressIsOurs = AddressIsOurs | ||
| AddressNotRecognised |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about reflecting the comment into the types as well? Rather than AddressNotRecognised
, should we have something like:
data AddressOwnership = AddressIsOurs
| AddressAmbiguousOwnership
probably not the best name in town, but would reflect the fact that the ownership is ambiguous: we really don't know if it's ours or not. AddressNotRecognised
suggest to me something a bit too strong.
|
||
instance ToJSON (V1 AddressIsOurs) where | ||
toJSON (V1 AddressIsOurs) = String "is-ours" | ||
toJSON (V1 AddressNotRecognised) = String "not-recognised" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have an (alas unwritten) conversion that we use snake case for enums, so it would be "is_ours", for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That can be done generically though with some helpers written by hand or taken from https://hackage.haskell.org/package/aeson-casing-0.1.0.5/docs/Data-Aeson-Casing.html
We probably already have a few stuff doing this.
instance FromJSON (V1 AddressIsOurs) where | ||
parseJSON (String "is-ours") = pure (V1 AddressIsOurs) | ||
parseJSON (String "not-recognised") = pure (V1 AddressNotRecognised) | ||
parseJSON x = typeMismatch "Not a valid AddressIsOurs" x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you haven't done so (haven't reached to that point of the PR perhaps) don't forget to write a roundtrip Aeson test inside the MarshallingSpecs
!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same remarks as above, what about leveraging generics here?
declareNamedSchema _ = | ||
pure $ NamedSchema (Just "V1AddressIsOurs") $ mempty | ||
& type_ .~ SwaggerString | ||
& enum_ ?~ ["is-ours", "not-recognised"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
snake_case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same about generics? Note that for now, our only option is with generics-sop
(debatable), applying the same transformation as in the JSON instance.
@@ -1221,12 +1259,14 @@ instance ToSchema WalletAddress where | |||
& ("id" --^ "Actual address.") | |||
& ("used" --^ "True if this address has been used.") | |||
& ("changeAddress" --^ "True if this address stores change from a previous transaction.") | |||
& ("isOurs" --^ "'is-ours' if this address is recognised as ours, 'not-recognised' if it has not (yet) been encountered") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking (perhaps @KtorZ can give his own cents): when we use the is
suffix usually that is used for booleans, but this one is not. Should we have rather a ownership
field? I think it sounds much better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. is
or has
prefix usually implies a boolean kind of value which is anyway a bad thing to have in an API 😛
I like ownership
but I'd rather have the haskell data-type match as well and renamed to AddressOwnership
if we go for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about?
data AddressOwnership = AddressOwnershipOurs | AddressOwnershipInconclusive
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and then on the API level: "ownership" -> "ours" or "inconclusive"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also happy with AddressOwnershipAmbiguous
To reflect this, the V1 endpoint does not fail when an address is not recognised | ||
and returns a new field 'is-ours' which indicates either that an address is ours, | ||
or that it is 'not-recognised' (which may mean 'not recognised yet'). | ||
|] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noice 👍
0c9994c
to
ae07766
Compare
Okay, updated 👍 |
@@ -27,7 +27,7 @@ headToLower [] = Nothing | |||
headToLower (x:xs) = Just $ toLower x : xs | |||
|
|||
stripFieldPrefix :: String -> String | |||
stripFieldPrefix = dropWhile (not . isUpper) | |||
stripFieldPrefix = dropWhile (not . isUpper) . drop 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOTE: I took some freedom in adjusting the way I think it should work. It was dead-code until now though, not used anywhere, so this change is free of any impact 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was just going to ask, cheers for clarifying 😁
wallet-new/test/MarshallingSpec.hs
Outdated
@@ -87,6 +87,7 @@ spec = parallel $ describe "Marshalling & Unmarshalling" $ do | |||
aesonRoundtripProp @SyncProgress Proxy | |||
aesonRoundtripProp @SyncThroughput Proxy | |||
aesonRoundtripProp @AccountIndex Proxy | |||
aesonRoundtripProp @AddressOwnership Proxy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I think about it, this shouldn't actually compile 🤔 ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(fixed)
ae07766
to
e492c45
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@KtorZ Thank you so much for taking this over from Ryan. Please don't hate me, but I have dropped a few comments 😅
@@ -1221,12 +1262,14 @@ instance ToSchema WalletAddress where | |||
& ("id" --^ "Actual address.") | |||
& ("used" --^ "True if this address has been used.") | |||
& ("changeAddress" --^ "True if this address stores change from a previous transaction.") | |||
& ("ownership" --^ "'isOurs' if this address is recognised as ours, 'ambiguousOwnership' if the node doesn't have information to make a unambiguous statement.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to make an unambiguous statement? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oui 🇫🇷
@@ -27,7 +27,7 @@ headToLower [] = Nothing | |||
headToLower (x:xs) = Just $ toLower x : xs | |||
|
|||
stripFieldPrefix :: String -> String | |||
stripFieldPrefix = dropWhile (not . isUpper) | |||
stripFieldPrefix = dropWhile (not . isUpper) . drop 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was just going to ask, cheers for clarifying 😁
-- it's not even ours. In both cases we return that it is "not used" | ||
-- (here) and "not a change address" (here). In the future we may want | ||
-- to extend this endpoint with an "is ours" field. | ||
-- it's not even ours. In both cases we return 'V1.AddressNotRecognised'. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ops, this comment should also be amended with the new type constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch 👍
@@ -51,7 +51,7 @@ createAddress wallet | |||
-- | Creates a new 'WalletAddress'. As this is a brand new, fresh Address, | |||
-- it's fine to have 'False' for both 'isUsed' and 'isChange'. | |||
mkAddress :: Address -> WalletAddress | |||
mkAddress addr = WalletAddress (V1 addr) False False | |||
mkAddress addr = WalletAddress (V1 addr) False False (V1 V1.AddressAmbiguousOwnership) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting default, but I cannot help but ponder -- are we sure that's a sensible one?
What happens now if I have just created a new address via Deadalus. Now this is neither used nor change, but it is mine.
But if we base everything about the helper function addressOwnershipFromUsed
that’s going to be a slipper slope I fear 😲
I was wondering whether mkAddress
should explicitly take an ownership argument so that you are forced to reason about ownership at the call site.
Plus, we would remove addressOwnershipFromUsed
as it's too easy perhaps to misuse it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally agree.
@@ -534,6 +534,7 @@ spec = describe "Addresses" $ do | |||
addrId = V1.V1 randomAddr | |||
, addrUsed = False | |||
, addrChangeAddress = False | |||
, addrIsOurs = V1.V1 V1.AddressNotRecognised |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this even compile? 😲
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Na 🙃
e492c45
to
d87f4f1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job, I like it @KtorZ 😉
If one member of your squad is out of work to do, a nice ticket might be to improve the tests for address ownership to actually test that if you pass an address from a different wallet, it comes across as inconclusive
and if you pass an address of a different account .. it should tell you it's yours as well? 🤔
addressMeta = acc ^. HD.hdAccountState . HD.hdAccountStateCurrent . cpAddressMeta cardanoAddress | ||
cardanoAddress = hdAddress ^. HD.hdAddressAddress . fromDb | ||
addressMeta = acc ^. HD.hdAccountState . HD.hdAccountStateCurrent . cpAddressMeta cardanoAddress | ||
addressOwnership = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Me and @KtorZ talked about this and we realised this is unnecessary complication: if we are converting from a kernel type (which remember, it's stored in our DB
to begin with), surely this is our address, because otherwise we wouldn't be able to construct an HDAddress
without having the derivation index of the parent account etc. This implies ownership, therefore here it's simply V1.AddressIsOurs
.
It's a good idea to bang a comment in the code as well to justify our rationale.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amended with what was discussed above 👍
…to clarify the ambiguity of an address not being recognised Co-authored-by: KtorZ
d87f4f1
to
43f4043
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solid! Thanks @KtorZ 😉
…e-address [CBR-401] For the validate-address API endpoint, add an IsOurs field …
…hk/feature/cbr-401-validate-address [CBR-401] For the validate-address API endpoint, add an IsOurs field …
…to clarify the ambiguity of an address not being recognised
Linked issue
https://iohk.myjetbrains.com/youtrack/issue/CBR-401
Type of change
Testing checklist