Skip to content

Commit db62cf0

Browse files
authored
Merge pull request #1109 from openmina/feat/grapqhl-adonagy
[GraphQL] add sendPayment and sendDelegation mutation
2 parents 30c9e68 + 4f6efdc commit db62cf0

File tree

7 files changed

+569
-79
lines changed

7 files changed

+569
-79
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node/native/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ redux = { workspace = true, features=["serializable_callbacks"] }
2222
ledger = { workspace = true }
2323
mina-p2p-messages = { workspace = true }
2424
mina-signer = { workspace = true }
25+
o1-utils = { workspace = true }
2526
bytes = "1.4.0"
2627
tracing-subscriber = { version = "0.3.17", features = ["json", "env-filter"] }
2728
tracing = "0.1.37"
@@ -33,6 +34,7 @@ jsonpath-rust = "0.5.0"
3334
sha3 = "0.10.8"
3435
strum = "0.26.2"
3536
strum_macros = "0.26.4"
37+
hex = { version = "0.4.3" }
3638

3739
openmina-core = { path = "../../core" }
3840
openmina-node-common = { path = "../common" }

node/native/src/graphql/mod.rs

Lines changed: 116 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ 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,
1413
rpc::{AccountQuery, RpcRequest, RpcSyncStatsGetResponse, SyncStatsQuery},
1514
stats::sync::SyncKind,
1615
};
16+
use o1_utils::field_helpers::FieldHelpersError;
1717
use openmina_core::block::AppliedBlock;
1818
use openmina_core::consensus::ConsensusConstants;
1919
use openmina_core::constants::constraint_constants;
@@ -26,6 +26,7 @@ pub mod account;
2626
pub mod block;
2727
pub mod constants;
2828
pub mod transaction;
29+
pub mod user_command;
2930
pub mod zkapp;
3031

3132
#[derive(Debug, thiserror::Error)]
@@ -54,6 +55,8 @@ pub enum ConversionError {
5455
InvalidDecimalNumber(#[from] mina_p2p_messages::bigint::InvalidDecimalNumber),
5556
#[error("Invalid bigint")]
5657
InvalidBigInt,
58+
#[error("Invalid hex")]
59+
InvalidHex,
5760
#[error(transparent)]
5861
ParseInt(#[from] std::num::ParseIntError),
5962
#[error(transparent)]
@@ -66,6 +69,14 @@ pub enum ConversionError {
6669
InvalidLength,
6770
#[error("Custom: {0}")]
6871
Custom(String),
72+
#[error(transparent)]
73+
FieldHelpers(#[from] FieldHelpersError),
74+
}
75+
76+
impl From<ConversionError> for Error {
77+
fn from(value: ConversionError) -> Self {
78+
Error::Conversion(value)
79+
}
6980
}
7081

7182
struct Context(RpcSender);
@@ -310,6 +321,56 @@ impl Query {
310321
}
311322
}
312323

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+
}
313374
#[derive(Clone, Debug)]
314375
struct Mutation;
315376

@@ -319,43 +380,64 @@ impl Mutation {
319380
input: zkapp::SendZkappInput,
320381
context: &Context,
321382
) -> juniper::FieldResult<zkapp::GraphQLSendZkappResponse> {
322-
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
323397
.0
324-
.oneshot_request(RpcRequest::TransactionInject(vec![input.try_into()?]))
398+
.oneshot_request(RpcRequest::LedgerAccountsGet(
399+
AccountQuery::PubKeyWithTokenId(public_key, token_id),
400+
))
325401
.await
326402
.ok_or(Error::StateMachineEmptyResponse)?;
327403

328-
match res {
329-
RpcTransactionInjectResponse::Success(res) => {
330-
let zkapp_cmd: MinaBaseUserCommandStableV2 = match res.first().cloned() {
331-
Some(RpcTransactionInjectedCommand::Zkapp(zkapp_cmd)) => zkapp_cmd.into(),
332-
_ => unreachable!(),
333-
};
334-
Ok(zkapp_cmd.try_into()?)
335-
}
336-
RpcTransactionInjectResponse::Rejected(rejected) => {
337-
let error_list = rejected
338-
.into_iter()
339-
.map(|(_, err)| graphql_value!({ "message": err.to_string() }))
340-
.collect::<Vec<_>>();
341-
342-
Err(FieldError::new(
343-
"Transaction rejected",
344-
graphql_value!(juniper::Value::List(error_list)),
345-
))
346-
}
347-
RpcTransactionInjectResponse::Failure(failure) => {
348-
let error_list = failure
349-
.into_iter()
350-
.map(|err| graphql_value!({ "message": err.to_string() }))
351-
.collect::<Vec<_>>();
352-
353-
Err(FieldError::new(
354-
"Transaction failed",
355-
graphql_value!(juniper::Value::List(error_list)),
356-
))
357-
}
358-
}
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
414+
}
415+
416+
async fn send_delegation(
417+
input: user_command::InputGraphQLDelegation,
418+
signature: user_command::UserCommandSignature,
419+
context: &Context,
420+
) -> juniper::FieldResult<user_command::GraphQLSendDelegationResponse> {
421+
// Payment commands are always for the default (MINA) token
422+
let token_id = TokenIdKeyHash::default();
423+
let public_key = AccountPublicKey::from_str(&input.from)?;
424+
425+
// Grab the sender's account to get the infered nonce
426+
let accounts: Vec<Account> = context
427+
.0
428+
.oneshot_request(RpcRequest::LedgerAccountsGet(
429+
AccountQuery::PubKeyWithTokenId(public_key, token_id),
430+
))
431+
.await
432+
.ok_or(Error::StateMachineEmptyResponse)?;
433+
434+
let infered_nonce = accounts
435+
.first()
436+
.ok_or(Error::StateMachineEmptyResponse)?
437+
.nonce;
438+
let command = input.create_user_command(infered_nonce, signature)?;
439+
440+
inject_tx(command, context).await
359441
}
360442
}
361443

0 commit comments

Comments
 (0)