Skip to content

Commit cbbdb7b

Browse files
author
Adrian Nagy
committed
feat(graphql): add sendDelegation mutation
1 parent 4ada2b7 commit cbbdb7b

File tree

4 files changed

+243
-110
lines changed

4 files changed

+243
-110
lines changed

node/native/src/graphql/mod.rs

Lines changed: 87 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use mina_p2p_messages::v2::MinaBaseUserCommandStableV2;
77
use mina_p2p_messages::v2::MinaBaseZkappCommandTStableV1WireStableV1;
88
use mina_p2p_messages::v2::TokenIdKeyHash;
99
use node::rpc::RpcTransactionInjectResponse;
10-
use node::rpc::RpcTransactionInjectedCommand;
1110
use node::rpc::{GetBlockQuery, RpcGetBlockResponse, RpcTransactionStatusGetResponse};
1211
use node::{
1312
account::AccountPublicKey,
@@ -74,6 +73,12 @@ pub enum ConversionError {
7473
FieldHelpers(#[from] FieldHelpersError),
7574
}
7675

76+
impl From<ConversionError> for Error {
77+
fn from(value: ConversionError) -> Self {
78+
Error::Conversion(value)
79+
}
80+
}
81+
7782
struct Context(RpcSender);
7883

7984
impl juniper::Context for Context {}
@@ -316,6 +321,56 @@ impl Query {
316321
}
317322
}
318323

324+
async fn inject_tx<R>(
325+
cmd: MinaBaseUserCommandStableV2,
326+
context: &Context,
327+
) -> juniper::FieldResult<R>
328+
where
329+
R: TryFrom<MinaBaseUserCommandStableV2>,
330+
{
331+
let res: RpcTransactionInjectResponse = context
332+
.0
333+
.oneshot_request(RpcRequest::TransactionInject(vec![cmd]))
334+
.await
335+
.ok_or(Error::StateMachineEmptyResponse)?;
336+
337+
match res {
338+
RpcTransactionInjectResponse::Success(res) => {
339+
let cmd: MinaBaseUserCommandStableV2 = match res.first().cloned() {
340+
Some(cmd) => cmd.into(),
341+
_ => unreachable!(),
342+
};
343+
cmd.try_into().map_err(|_| {
344+
FieldError::new(
345+
"Failed to convert transaction to the required type".to_string(),
346+
graphql_value!(null),
347+
)
348+
})
349+
}
350+
RpcTransactionInjectResponse::Rejected(rejected) => {
351+
let error_list = rejected
352+
.into_iter()
353+
.map(|(_, err)| graphql_value!({ "message": err.to_string() }))
354+
.collect::<Vec<_>>();
355+
356+
Err(FieldError::new(
357+
"Transaction rejected",
358+
graphql_value!(juniper::Value::List(error_list)),
359+
))
360+
}
361+
RpcTransactionInjectResponse::Failure(failure) => {
362+
let error_list = failure
363+
.into_iter()
364+
.map(|err| graphql_value!({ "message": err.to_string() }))
365+
.collect::<Vec<_>>();
366+
367+
Err(FieldError::new(
368+
"Transaction failed",
369+
graphql_value!(juniper::Value::List(error_list)),
370+
))
371+
}
372+
}
373+
}
319374
#[derive(Clone, Debug)]
320375
struct Mutation;
321376

@@ -325,50 +380,44 @@ impl Mutation {
325380
input: zkapp::SendZkappInput,
326381
context: &Context,
327382
) -> juniper::FieldResult<zkapp::GraphQLSendZkappResponse> {
328-
let res: RpcTransactionInjectResponse = context
383+
inject_tx(input.try_into()?, context).await
384+
}
385+
386+
async fn send_payment(
387+
input: user_command::InputGraphQLPayment,
388+
signature: user_command::UserCommandSignature,
389+
context: &Context,
390+
) -> juniper::FieldResult<user_command::GraphQLSendPaymentResponse> {
391+
// Grab the sender's account to get the infered nonce
392+
let token_id = TokenIdKeyHash::default();
393+
let public_key = AccountPublicKey::from_str(&input.from)
394+
.map_err(|e| Error::Conversion(ConversionError::Base58Check(e)))?;
395+
396+
let accounts: Vec<Account> = context
329397
.0
330-
.oneshot_request(RpcRequest::TransactionInject(vec![input.try_into()?]))
398+
.oneshot_request(RpcRequest::LedgerAccountsGet(
399+
AccountQuery::PubKeyWithTokenId(public_key, token_id),
400+
))
331401
.await
332402
.ok_or(Error::StateMachineEmptyResponse)?;
333403

334-
match res {
335-
RpcTransactionInjectResponse::Success(res) => {
336-
let zkapp_cmd: MinaBaseUserCommandStableV2 = match res.first().cloned() {
337-
Some(RpcTransactionInjectedCommand::Zkapp(zkapp_cmd)) => zkapp_cmd.into(),
338-
_ => unreachable!(),
339-
};
340-
Ok(zkapp_cmd.try_into()?)
341-
}
342-
RpcTransactionInjectResponse::Rejected(rejected) => {
343-
let error_list = rejected
344-
.into_iter()
345-
.map(|(_, err)| graphql_value!({ "message": err.to_string() }))
346-
.collect::<Vec<_>>();
347-
348-
Err(FieldError::new(
349-
"Transaction rejected",
350-
graphql_value!(juniper::Value::List(error_list)),
351-
))
352-
}
353-
RpcTransactionInjectResponse::Failure(failure) => {
354-
let error_list = failure
355-
.into_iter()
356-
.map(|err| graphql_value!({ "message": err.to_string() }))
357-
.collect::<Vec<_>>();
358-
359-
Err(FieldError::new(
360-
"Transaction failed",
361-
graphql_value!(juniper::Value::List(error_list)),
362-
))
363-
}
364-
}
404+
let infered_nonce = accounts
405+
.first()
406+
.ok_or(Error::StateMachineEmptyResponse)?
407+
.nonce;
408+
409+
let command = input
410+
.create_user_command(infered_nonce, signature)
411+
.map_err(Error::Conversion)?;
412+
413+
inject_tx(command, context).await
365414
}
366415

367-
async fn send_payment(
368-
input: user_command::InputGraphQLPayment,
416+
async fn send_delegation(
417+
input: user_command::InputGraphQLDelegation,
369418
signature: user_command::UserCommandSignature,
370419
context: &Context,
371-
) -> juniper::FieldResult<user_command::GraphQLSendPaymentResponse> {
420+
) -> juniper::FieldResult<user_command::GraphQLSendDelegationResponse> {
372421
// Payment commands are always for the default (MINA) token
373422
let token_id = TokenIdKeyHash::default();
374423
let public_key = AccountPublicKey::from_str(&input.from)?;
@@ -388,43 +437,7 @@ impl Mutation {
388437
.nonce;
389438
let command = input.create_user_command(infered_nonce, signature)?;
390439

391-
let res: RpcTransactionInjectResponse = context
392-
.0
393-
.oneshot_request(RpcRequest::TransactionInject(vec![command]))
394-
.await
395-
.ok_or(Error::StateMachineEmptyResponse)?;
396-
397-
match res {
398-
RpcTransactionInjectResponse::Success(res) => {
399-
let payment_cmd: MinaBaseUserCommandStableV2 = match res.first().cloned() {
400-
Some(RpcTransactionInjectedCommand::Payment(payment)) => payment.into(),
401-
_ => unreachable!(),
402-
};
403-
Ok(payment_cmd.try_into()?)
404-
}
405-
RpcTransactionInjectResponse::Rejected(rejected) => {
406-
let error_list = rejected
407-
.into_iter()
408-
.map(|(_, err)| graphql_value!({ "message": err.to_string() }))
409-
.collect::<Vec<_>>();
410-
411-
Err(FieldError::new(
412-
"Transaction rejected",
413-
graphql_value!(juniper::Value::List(error_list)),
414-
))
415-
}
416-
RpcTransactionInjectResponse::Failure(failure) => {
417-
let error_list = failure
418-
.into_iter()
419-
.map(|err| graphql_value!({ "message": err.to_string() }))
420-
.collect::<Vec<_>>();
421-
422-
Err(FieldError::new(
423-
"Transaction failed",
424-
graphql_value!(juniper::Value::List(error_list)),
425-
))
426-
}
427-
}
440+
inject_tx(command, context).await
428441
}
429442
}
430443

node/native/src/graphql/user_command.rs

Lines changed: 127 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ pub struct InputGraphQLPayment {
3232
pub nonce: Option<String>,
3333
}
3434

35+
#[derive(GraphQLInputObject, Debug)]
36+
pub struct InputGraphQLDelegation {
37+
pub from: String,
38+
pub to: String,
39+
pub valid_until: Option<String>,
40+
pub fee: String,
41+
pub memo: Option<String>,
42+
pub nonce: Option<String>,
43+
}
44+
3545
#[derive(GraphQLInputObject, Debug, Clone)]
3646
pub struct UserCommandSignature {
3747
pub field: Option<String>,
@@ -108,6 +118,11 @@ pub struct GraphQLSendPaymentResponse {
108118
pub payment: GraphQLUserCommand,
109119
}
110120

121+
#[derive(GraphQLObject, Debug)]
122+
pub struct GraphQLSendDelegationResponse {
123+
pub delegation: GraphQLUserCommand,
124+
}
125+
111126
#[derive(GraphQLObject, Debug)]
112127
pub struct GraphQLUserCommand {
113128
pub amount: String,
@@ -147,7 +162,7 @@ impl TryFrom<v2::MinaBaseUserCommandStableV2> for GraphQLSendPaymentResponse {
147162
hash: signed_cmd.hash()?.to_string(),
148163
id: signed_cmd.to_base64()?,
149164
is_delegation: false,
150-
kind: "Payment".to_string(),
165+
kind: "PAYMENT".to_string(),
151166
memo: signed_cmd.payload.common.memo.to_base58check(),
152167
nonce: signed_cmd.payload.common.nonce.to_string(),
153168
receiver: payment.receiver_pk.to_string(),
@@ -166,6 +181,43 @@ impl TryFrom<v2::MinaBaseUserCommandStableV2> for GraphQLSendPaymentResponse {
166181
}
167182
}
168183

184+
impl TryFrom<v2::MinaBaseUserCommandStableV2> for GraphQLSendDelegationResponse {
185+
type Error = super::ConversionError;
186+
fn try_from(value: v2::MinaBaseUserCommandStableV2) -> Result<Self, Self::Error> {
187+
if let v2::MinaBaseUserCommandStableV2::SignedCommand(ref signed_cmd) = value {
188+
if let v2::MinaBaseSignedCommandPayloadBodyStableV2::StakeDelegation(ref delegation) =
189+
signed_cmd.payload.body
190+
{
191+
let v2::MinaBaseStakeDelegationStableV2::SetDelegate { new_delegate } = delegation;
192+
let res = GraphQLSendDelegationResponse {
193+
delegation: GraphQLUserCommand {
194+
amount: "0".to_string(),
195+
fee: signed_cmd.payload.common.fee.to_string(),
196+
failure_reason: None,
197+
fee_payer: signed_cmd.payload.common.fee_payer_pk.to_string(),
198+
fee_token: TokenIdKeyHash::default().to_string(),
199+
hash: signed_cmd.hash()?.to_string(),
200+
id: signed_cmd.to_base64()?,
201+
is_delegation: true,
202+
kind: "STAKE_DELEGATION".to_string(),
203+
memo: signed_cmd.payload.common.memo.to_base58check(),
204+
nonce: signed_cmd.payload.common.nonce.to_string(),
205+
receiver: new_delegate.to_string(),
206+
source: signed_cmd.payload.common.fee_payer_pk.to_string(),
207+
token: TokenIdKeyHash::default().to_string(),
208+
valid_until: signed_cmd.payload.common.valid_until.as_u32().to_string(),
209+
},
210+
};
211+
Ok(res)
212+
} else {
213+
Err(super::ConversionError::WrongVariant)
214+
}
215+
} else {
216+
Err(super::ConversionError::WrongVariant)
217+
}
218+
}
219+
}
220+
169221
impl InputGraphQLPayment {
170222
pub fn create_user_command(
171223
&self,
@@ -244,11 +296,79 @@ impl InputGraphQLPayment {
244296
Ok(v2::MinaBaseUserCommandStableV2::SignedCommand(sc.into()))
245297
}
246298
}
247-
// impl TryFrom<InputGraphQLSendPayment> for v2::MinaBaseUserCommandStableV2 {
248-
// type Error = super::ConversionError;
249299

250-
// fn try_from(value: InputGraphQLSendPayment) -> Result<Self, Self::Error> {
251-
// let InputGraphQLSendPayment { input, signature } = value;
300+
impl InputGraphQLDelegation {
301+
pub fn create_user_command(
302+
&self,
303+
infered_nonce: Nonce,
304+
signature: UserCommandSignature,
305+
) -> Result<v2::MinaBaseUserCommandStableV2, super::ConversionError> {
306+
let infered_nonce = infered_nonce.incr();
252307

253-
// }
254-
// }
308+
let nonce = if let Some(nonce) = &self.nonce {
309+
let input_nonce = Nonce::from_u32(
310+
nonce
311+
.parse::<u32>()
312+
.map_err(|_| super::ConversionError::InvalidBigInt)?,
313+
);
314+
315+
if input_nonce.is_zero() || input_nonce > infered_nonce {
316+
return Err(super::ConversionError::Custom(
317+
"Provided nonce is zero or greater than infered nonce".to_string(),
318+
));
319+
} else {
320+
input_nonce
321+
}
322+
} else {
323+
infered_nonce
324+
};
325+
326+
let valid_until = if let Some(valid_until) = &self.valid_until {
327+
Some(Slot::from_u32(
328+
valid_until
329+
.parse::<u32>()
330+
.map_err(|_| super::ConversionError::InvalidBigInt)?,
331+
))
332+
} else {
333+
None
334+
};
335+
336+
let memo = if let Some(memo) = &self.memo {
337+
Memo::from_str(memo)
338+
.map_err(|_| super::ConversionError::Custom("Invalid memo".to_string()))?
339+
} else {
340+
Memo::empty()
341+
};
342+
343+
let from: CompressedPubKey = AccountPublicKey::from_str(&self.from)?
344+
.try_into()
345+
.map_err(|_| super::ConversionError::InvalidBigInt)?;
346+
347+
let signature = signature.try_into()?;
348+
349+
let sc: signed_command::SignedCommand = signed_command::SignedCommand {
350+
payload: signed_command::SignedCommandPayload::create(
351+
Fee::from_u64(
352+
self.fee
353+
.parse::<u64>()
354+
.map_err(|_| super::ConversionError::InvalidBigInt)?,
355+
),
356+
from.clone(),
357+
nonce,
358+
valid_until,
359+
memo,
360+
signed_command::Body::StakeDelegation(
361+
signed_command::StakeDelegationPayload::SetDelegate {
362+
new_delegate: AccountPublicKey::from_str(&self.to)?
363+
.try_into()
364+
.map_err(|_| super::ConversionError::InvalidBigInt)?,
365+
},
366+
),
367+
),
368+
signer: from.clone(),
369+
signature,
370+
};
371+
372+
Ok(v2::MinaBaseUserCommandStableV2::SignedCommand(sc.into()))
373+
}
374+
}

0 commit comments

Comments
 (0)