-
Notifications
You must be signed in to change notification settings - Fork 217
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
Add transaction signing API endpoint with stub impl #2642
Conversation
-> ((k 'RootK XPrv, Passphrase "encryption") -> (XPrv, Passphrase "encryption")) | ||
-- ^ Reward account derived from the root key (or somewhere else). | ||
-> Passphrase "raw" | ||
-> ByteString |
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.
Maybe introduce newtype TxBody
rather than ByteString
?
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.
Yes it needs some type, but that will be added in the next PR. This function is a placeholder stub, so I hope that ByteString is good enough for the moment.
lib/core/src/Cardano/Wallet/Api.hs
Outdated
:> "transactions" | ||
:> "sign" | ||
:> ReqBody '[JSON] PostSignTransactionData | ||
:> PostAccepted '[JSON, OctetStream] ApiSerialisedTransaction |
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 want both json and octet, not just octet?
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.
Is it connected with the fact that we want both binary (serviced by octet) and hex string (serviced by json)?
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.
The user can decide whether they want raw transaction bytes, or hex-encoded bytes inside a JSON object.
data PostTransactionData (n :: NetworkDiscriminant) = PostTransactionData | ||
data PostSignTransactionData = PostSignTransactionData | ||
{ txBody :: !ApiSerialisedTransaction -- TODO: ADP-902 or tx | ||
, passphrase :: !(ApiT (Passphrase "lenient")) |
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.
"lenient" here 🤔
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.
It means that there is no minimum length for the given passphrase.
We only want a minimum length when creating a new wallet or changing its password.
instance FromJSON ApiSerialisedTransaction where | ||
parseJSON = fmap ApiSerialisedTransaction . fromBaseText Base16 | ||
instance ToJSON ApiSerialisedTransaction where | ||
toJSON = toBaseText Base16 . view #payload |
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.
very elegant I must say:-)
specifications/api/swagger.yaml
Outdated
title: Serialized transaction witnesses | ||
|
||
ApiSerialisedTransaction: &ApiSerialisedTransaction | ||
oneOf: |
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.
ah, I got it now, why [JSON, OctetStream]
specifications/api/swagger.yaml
Outdated
/wallets/{walletId}/transactions: | ||
post: | ||
operationId: postTransaction | ||
tags: ["Transactions"] | ||
tags: ["Legacy Transactions"] |
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 waiting with Transactions -> Legacy Transactions
and stable -> deprecated
until the last ticket in this epic?
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.
OK fair enough.
specifications/api/swagger.yaml
Outdated
required: | ||
- body | ||
- wits | ||
properties: |
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 should I understand this? Either we get serialized hex which means all needed witnesses were constructed and added and the signed txs can be submitted OR I have partially signed tx, ie., not enough witnesses collected yet to treat it as fully signed tx?
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 added some more swagger doc strings.
The idea is that the user can submit an encoded tx, or an encoded tx body with zero or more witnesses.
The signing endpoint will add new witnesses to the set.
@rvl we have now
What about creating serialized tx body from outputs, delegations, ... It is in another ticket, right? |
Thanks for the review comments @paweljakubas . You were probably slightly confused because I made an error with the return types. Constructing the TxBody is for another PR, ticket ADP-909. |
3e6c7f9
to
78da2db
Compare
lib/core/src/Cardano/Wallet/Api.hs
Outdated
type SignTransaction n = "wallets" | ||
:> Capture "walletId" (ApiT WalletId) | ||
:> "transactions" | ||
:> "sign" |
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 signatures
to be a bit more rest-ish. Verbs / action should ideally be captured by the HTTP method.
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'm afraid that REST is a poor fit for this endpoint (perhaps in general too, but that's a separate discussion).
The resource is the wallet (specifically the keys contained in the wallet) and action is to sign a blob of data (a TxBody), given a spending password, and return the blob of data plus its signatures (TxBody + TxWits).
None of the HTTPs methods adequately describe this signing operation. And no request path name can make a verb look like a noun.
So I think the least worst name is something short and simple: POST /v2/wallet/<id>/transactions/sign
.
Slightly related, I think the corresponding servant handler function name I have chosen is thoroughly confusing: postSignTransaction
. What do you think of renaming that to signTransactionHandler
?
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.
(perhaps in general too, but that's a separate discussion).
I couldn't agree more.
None of the HTTPs methods adequately describe this signing operation. And no request path name can make a verb look like a noun.
I see it differently 🤔 , to me POST /wallets/{walletId}/witnesses
for instance would read just fine. This creates a witness
resource from a given payload on the given wallet
. So it's kinda rest-ish.
Slightly related, I think the corresponding servant handler function name I have chosen is thoroughly confusing: postSignTransaction. What do you think of renaming that to signTransactionHandler ?
Yeah... I always have mixed feelings when naming handlers. One the one hand, keeping some form of consistent naming from the endpoint structure makes it "brain dead" and easy to name / figure out. But on the other hand, it's often ugly and, we have deviated so many times from it that we have lost any benefits from consistent naming. This being said, I like your suggestion 😊
@@ -778,22 +779,27 @@ data ByronWalletPutPassphraseData = ByronWalletPutPassphraseData | |||
, newPassphrase :: !(ApiT (Passphrase "raw")) | |||
} deriving (Eq, Generic, Show) | |||
|
|||
data PostTransactionData (n :: NetworkDiscriminant) = PostTransactionData | |||
data PostSignTransactionData = PostSignTransactionData | |||
{ tx :: !ApiSerialisedTransaction -- TODO: ADP-902 or tx |
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've avoided shorten names and acronym in the API so far. I'd suggest to go for "transaction" or "body" since this is really just the transaction body without witnesses in principle.
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.
Yep good idea, fixed.
@@ -2072,9 +2078,19 @@ instance ToJSON (ApiT BoundType) where | |||
instance FromJSON (ApiT BoundType) where | |||
parseJSON = fmap ApiT . genericParseJSON defaultSumTypeOptions | |||
|
|||
instance DecodeAddress t => FromJSON (PostTransactionData t) where | |||
instance FromJSON ApiSerialisedTransaction where | |||
parseJSON = fmap ApiSerialisedTransaction . fromBaseText Base16 |
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.
Wouldn't Base64 be a better fit here? We typically use Base16 for short-ish strings and base64 for longer payloads.
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 don't think it matters that much. I thought hex might be slightly easier to use. Base64 is 1.33× raw size and hex is 2× raw size - both acceptable for transactions given their max size. Anyway I have changed it to Base64 because the sign metadata endpoint uses Base64, as you say. I also changed the encoding accepted by the cardano-wallet transaction submit
CLI from hex to base64 to align with this.
jsonValid = first (BodyParam . Aeson.encode) <$> paymentCases ++ | ||
[ -- passphrase | ||
( [aesonQQ| | ||
{ "tx_body": "cafecafe" |
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 didn't catch where the code from Api/Types could generate a tx_body
field 🤔 ?
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.
Yes I changed the types and forgot to update these tests.
instance | ||
( ReflectMethod m | ||
, Accept ct | ||
) => GEveryEndpoints (Verb (m :: StdMethod) s '[ct] a) | ||
, GEveryEndpoints (Verb m s cts a) | ||
) => GEveryEndpoints (Verb (m :: StdMethod) s (ct ': cts) a) |
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.
👍
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.
lgtm, the rest bits can come in next PRs
b4f682d
to
6e4a0e0
Compare
4d6959f
to
ace76e5
Compare
ace76e5
to
aac5f1d
Compare
-- | @SerialisedTxParts@ is a serialised transaction body, and a possibly | ||
-- incomplete set of serialised witnesses, along with an encoding of the | ||
-- combined transaction. | ||
data SerialisedTxParts = SerialisedTxParts |
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.
Why is SerialisedTxParts
needed when we have SerialisedTx
?
Isn't the structure it exposes already unambiguously exposed in the raw CBOR?
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.
Yes, but I think it will be helpful for API consumers to get both.
c5a072a
to
e5e832b
Compare
e5e832b
to
c260987
Compare
c260987
to
3b80538
Compare
3b80538
to
554f07a
Compare
554f07a
to
a365f05
Compare
bors r+ |
) | ||
=> ctx | ||
-> WalletId | ||
-> ((k 'RootK XPrv, Passphrase "encryption") -> (XPrv, Passphrase "encryption")) |
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.
well, as I did in construct tx PR, one can argue we can read public reward account key from wallet state, we need this structure only when private reward account key is needed...which probably is the case here
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.
much work is needed to implement signing and prove it works which is to be done in coming PRs. API seems reasonable, some solid/beautifully-conveying-idea-under-the-hood data structures were introduced
Build succeeded: |
Issue Number
ADP-902
Overview
TODO
comments.