From 3fbb2a221fd5dafba662055c0a2d299304be6e34 Mon Sep 17 00:00:00 2001 From: piotr-iohk <42900201+piotr-iohk@users.noreply.github.com> Date: Wed, 4 Dec 2024 08:20:41 +0100 Subject: [PATCH] Testing and adjustments for /account/balance (#78) --- ...7cbca4cf1ba7f056d3ce768ed9f1850a63de.json} | 12 ++- sql/queries/maybe_account_balance_info.sql | 3 +- src/api/account_balance.rs | 88 +++++++++++-------- src/create_router.rs | 20 ++--- src/graphql/balance.graphql | 1 + src/operation.rs | 25 +++--- tests/compare_to_ocaml.rs | 12 +++ tests/fixtures/account_balance.rs | 26 ++++-- tests/fixtures/block.rs | 8 +- tests/fixtures/mempool.rs | 8 +- tests/fixtures/mod.rs | 10 ++- tests/fixtures/network.rs | 7 +- .../snapshots/account_balance__responses.snap | 26 ++++-- 13 files changed, 148 insertions(+), 98 deletions(-) rename .sqlx/{query-c649a3d2ca4db1b366f89e0e8962f7364c3c7326007a2c8b6120e163a482618f.json => query-4c5fd3b9aac79b863e5d90b531007cbca4cf1ba7f056d3ce768ed9f1850a63de.json} (58%) diff --git a/.sqlx/query-c649a3d2ca4db1b366f89e0e8962f7364c3c7326007a2c8b6120e163a482618f.json b/.sqlx/query-4c5fd3b9aac79b863e5d90b531007cbca4cf1ba7f056d3ce768ed9f1850a63de.json similarity index 58% rename from .sqlx/query-c649a3d2ca4db1b366f89e0e8962f7364c3c7326007a2c8b6120e163a482618f.json rename to .sqlx/query-4c5fd3b9aac79b863e5d90b531007cbca4cf1ba7f056d3ce768ed9f1850a63de.json index 051a8c6..dc5698f 100644 --- a/.sqlx/query-c649a3d2ca4db1b366f89e0e8962f7364c3c7326007a2c8b6120e163a482618f.json +++ b/.sqlx/query-4c5fd3b9aac79b863e5d90b531007cbca4cf1ba7f056d3ce768ed9f1850a63de.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT\n b.height,\n b.global_slot_since_genesis AS block_global_slot_since_genesis,\n balance,\n nonce,\n timing_id\nFROM\n blocks b\n INNER JOIN accounts_accessed ac ON ac.block_id=b.id\n INNER JOIN account_identifiers ai ON ai.id=ac.account_identifier_id\n INNER JOIN public_keys pks ON ai.public_key_id=pks.id\n INNER JOIN tokens t ON ai.token_id=t.id\nWHERE\n pks.value=$1\n AND b.height<=$2\n AND b.chain_status='canonical'\n AND t.value=$3\nORDER BY\n (b.height) DESC\nLIMIT\n 1\n", + "query": "SELECT\n b.height,\n b.global_slot_since_genesis AS block_global_slot_since_genesis,\n balance,\n nonce,\n timing_id,\n t.value AS token_id\nFROM\n blocks b\n INNER JOIN accounts_accessed ac ON ac.block_id=b.id\n INNER JOIN account_identifiers ai ON ai.id=ac.account_identifier_id\n INNER JOIN public_keys pks ON ai.public_key_id=pks.id\n INNER JOIN tokens t ON ai.token_id=t.id\nWHERE\n pks.value=$1\n AND b.height<=$2\n AND b.chain_status='canonical'\n AND t.value=$3\nORDER BY\n (b.height) DESC\nLIMIT\n 1\n", "describe": { "columns": [ { @@ -27,6 +27,11 @@ "ordinal": 4, "name": "timing_id", "type_info": "Int4" + }, + { + "ordinal": 5, + "name": "token_id", + "type_info": "Text" } ], "parameters": { @@ -41,8 +46,9 @@ false, false, false, - true + true, + false ] }, - "hash": "c649a3d2ca4db1b366f89e0e8962f7364c3c7326007a2c8b6120e163a482618f" + "hash": "4c5fd3b9aac79b863e5d90b531007cbca4cf1ba7f056d3ce768ed9f1850a63de" } diff --git a/sql/queries/maybe_account_balance_info.sql b/sql/queries/maybe_account_balance_info.sql index d4b4f54..2f00146 100644 --- a/sql/queries/maybe_account_balance_info.sql +++ b/sql/queries/maybe_account_balance_info.sql @@ -3,7 +3,8 @@ SELECT b.global_slot_since_genesis AS block_global_slot_since_genesis, balance, nonce, - timing_id + timing_id, + t.value AS token_id FROM blocks b INNER JOIN accounts_accessed ac ON ac.block_id=b.id diff --git a/src/api/account_balance.rs b/src/api/account_balance.rs index dfdb628..4a369f9 100644 --- a/src/api/account_balance.rs +++ b/src/api/account_balance.rs @@ -1,11 +1,13 @@ use coinbase_mesh::models::{ - AccountBalanceRequest, AccountBalanceResponse, AccountIdentifier, Amount, BlockIdentifier, Currency, - PartialBlockIdentifier, + AccountBalanceRequest, AccountBalanceResponse, AccountIdentifier, Amount, BlockIdentifier, PartialBlockIdentifier, }; use cynic::QueryBuilder; use crate::{ - graphql::{Account, AnnotatedBalance, Balance, Length, QueryBalance, QueryBalanceVariables, StateHash}, + create_currency, + graphql::{ + Account, AccountNonce, AnnotatedBalance, Balance, Length, QueryBalance, QueryBalanceVariables, StateHash, TokenId, + }, util::Wrapper, MinaMesh, MinaMeshError, }; @@ -42,22 +44,25 @@ impl MinaMesh { .fetch_optional(&self.pg_pool) .await?; match maybe_account_balance_info { - None => { - Ok(AccountBalanceResponse::new(BlockIdentifier { hash: block.state_hash, index: block.height }, vec![Amount { - currency: Box::new(Currency { - symbol: "MINA".into(), // TODO: Use actual currency symbol / custom tokens - decimals: 9, - metadata: None, - }), + None => Ok(AccountBalanceResponse { + block_identifier: Box::new(BlockIdentifier { hash: block.state_hash, index: block.height }), + balances: vec![Amount { + currency: Box::new(create_currency(None)), value: "0".to_string(), metadata: Some(serde_json::json!({ - "locked_balance": "0".to_string(), - "liquid_balance": "0".to_string(), - "total_balance": "0".to_string() + "locked_balance": 0, + "liquid_balance": 0, + "total_balance": 0 })), - }])) - } + }], + metadata: Some(serde_json::json!({ + "created_via_historical_lookup": true, + "nonce": "0" + })), + }), Some(account_balance_info) => { + let token_id = account_balance_info.token_id; + let nonce = account_balance_info.nonce; let last_relevant_command_balance = account_balance_info.balance.parse::()?; let timing_info = sqlx::query_file!("sql/queries/timing_info.sql", account_balance_info.timing_id) .fetch_optional(&self.pg_pool) @@ -79,19 +84,22 @@ impl MinaMesh { }; let total_balance = last_relevant_command_balance; let locked_balance = total_balance - liquid_balance; - Ok(AccountBalanceResponse::new(BlockIdentifier { hash: block.state_hash, index: block.height }, vec![Amount { - currency: Box::new(Currency { - symbol: "MINA".into(), // TODO: Use actual currency symbol / custom tokens - decimals: 9, - metadata: None, - }), - value: liquid_balance.to_string(), + Ok(AccountBalanceResponse { + block_identifier: Box::new(BlockIdentifier { hash: block.state_hash, index: block.height }), + balances: vec![Amount { + currency: Box::new(create_currency(Some(&token_id))), + value: liquid_balance.to_string(), + metadata: Some(serde_json::json!({ + "locked_balance": locked_balance, + "liquid_balance": liquid_balance, + "total_balance": total_balance + })), + }], metadata: Some(serde_json::json!({ - "locked_balance": locked_balance.to_string(), - "liquid_balance": liquid_balance.to_string(), - "total_balance": total_balance.to_string() + "created_via_historical_lookup": true, + "nonce": format!("{}", nonce) })), - }])) + }) } } } @@ -111,26 +119,30 @@ impl MinaMesh { liquid: Some(Balance(liquid_raw)), total: Balance(total_raw), }, - .. + nonce: Some(AccountNonce(nonce)), + token_id: TokenId(token_id), }), } = result { let total = total_raw.parse::()?; let liquid = liquid_raw.parse::()?; let index = index_raw.parse::()?; - Ok(AccountBalanceResponse::new(BlockIdentifier { hash, index }, vec![Amount { - currency: Box::new(Currency { - symbol: "MINA".into(), // TODO: Use actual currency symbol / custom tokens - decimals: 9, - metadata: None, - }), - value: total_raw, + Ok(AccountBalanceResponse { + block_identifier: Box::new(BlockIdentifier { hash, index }), + balances: vec![Amount { + currency: Box::new(create_currency(Some(&token_id))), + value: total_raw, + metadata: Some(serde_json::json!({ + "locked_balance": (total - liquid), + "liquid_balance": liquid, + "total_balance": total + })), + }], metadata: Some(serde_json::json!({ - "locked_balance": (total - liquid).to_string(), - "liquid_balance": liquid.to_string(), - "total_balance": total.to_string() + "created_via_historical_lookup": false, + "nonce": format!("{}", nonce) })), - }])) + }) } else { Err(MinaMeshError::AccountNotFound(public_key)) } diff --git a/src/create_router.rs b/src/create_router.rs index 67c0e0d..7990928 100644 --- a/src/create_router.rs +++ b/src/create_router.rs @@ -13,6 +13,7 @@ use crate::{playground::handle_playground, util::Wrapper, MinaMesh, MinaMeshErro pub fn create_router(mina_mesh: MinaMesh, playground: bool) -> Router { let mut router = Router::new() + .route("/available_endpoints", get(handle_available_endpoints)) .route("/account/balance", post(handle_account_balance)) .route("/block", post(handle_block)) .route("/call", post(handle_call)) @@ -24,7 +25,6 @@ pub fn create_router(mina_mesh: MinaMesh, playground: bool) -> Router { .route("/construction/payloads", post(handle_construction_payloads)) .route("/construction/preprocess", post(handle_construction_preprocess)) .route("/construction/submit", post(handle_construction_submit)) - .route("/implemented_methods", get(handle_implemented_methods)) .route("/mempool", post(handle_mempool)) .route("/mempool/transaction", post(handle_mempool_transaction)) .route("/network/list", post(handle_network_list)) @@ -77,15 +77,15 @@ create_handler!(network_status, NetworkRequest); create_handler!(search_transactions, SearchTransactionsRequest); #[debug_handler] -async fn handle_implemented_methods() -> impl IntoResponse { +async fn handle_available_endpoints() -> impl IntoResponse { Json([ - "account_balance", - "block", - "mempool", - "mempool_transaction", - "network_list", - "network_options", - "network_status", - "search_transactions", + "/account/balance", + "/block", + "/mempool", + "/mempool/transaction", + "/network/list", + "/network/options", + "/network/status", + "/search/transactions", ]) } diff --git a/src/graphql/balance.graphql b/src/graphql/balance.graphql index 65d648d..4763ae0 100644 --- a/src/graphql/balance.graphql +++ b/src/graphql/balance.graphql @@ -7,5 +7,6 @@ query QueryBalance($publicKey: PublicKey!) { total } nonce + tokenId } } diff --git a/src/operation.rs b/src/operation.rs index 90d6bad..5ac56e5 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -4,6 +4,18 @@ use serde_json::json; use crate::{util::DEFAULT_TOKEN_ID, OperationStatus, OperationType, TransactionStatus}; +/// Creates a `Currency` based on the token provided. +/// If the token is `DEFAULT_TOKEN_ID`, it creates a MINA currency. +/// Otherwise, it creates a MINA+ currency with the token ID in metadata. +pub fn create_currency(token: Option<&String>) -> Currency { + match token { + Some(token_id) if token_id != DEFAULT_TOKEN_ID => { + Currency { symbol: "MINA+".to_owned(), decimals: 9, metadata: Some(json!({ "token_id": token_id })) } + } + _ => Currency::new("MINA".to_owned(), 9), + } +} + #[allow(clippy::too_many_arguments)] pub fn operation( ident: i64, @@ -15,18 +27,7 @@ pub fn operation( metadata: Option<&serde_json::Value>, token: Option<&String>, ) -> Operation { - // if token is provided and different from DEFAULT_TOKEN_ID, then create a new - // currency with the token else create a new currency with "MINA" - let currency = token - .map(|token_id| { - if token_id != DEFAULT_TOKEN_ID { - Currency { symbol: "MINA+".to_owned(), decimals: 9, metadata: Some(json!({ "token_id": token_id })) } - } else { - Currency::new("MINA".to_owned(), 9) - } - }) - .unwrap_or(Currency::new("MINA".to_owned(), 9)); - + let currency = create_currency(token); Operation { operation_identifier: Box::new(OperationIdentifier::new(ident)), amount: amount.map(|value| Box::new(Amount::new(value.to_owned(), currency))), diff --git a/tests/compare_to_ocaml.rs b/tests/compare_to_ocaml.rs index 98d7b8a..bd9dd59 100644 --- a/tests/compare_to_ocaml.rs +++ b/tests/compare_to_ocaml.rs @@ -68,3 +68,15 @@ async fn mempool_transaction() -> Result<()> { let (subpath, reqs) = fixtures::mempool_transaction(); assert_responses_contain(subpath, &reqs, "\"message\": \"Transaction not found").await } + +#[tokio::test] +async fn account_balance() -> Result<()> { + let (subpath, reqs) = fixtures::account_balance(); + assert_responses_eq(subpath, &reqs).await +} + +#[tokio::test] +async fn account_balance_not_exists() -> Result<()> { + let (subpath, reqs) = fixtures::account_balance_not_exists(); + assert_responses_contain(subpath, &reqs, "\"message\": \"Account not found").await +} diff --git a/tests/fixtures/account_balance.rs b/tests/fixtures/account_balance.rs index e35dba4..5cdf565 100644 --- a/tests/fixtures/account_balance.rs +++ b/tests/fixtures/account_balance.rs @@ -1,16 +1,16 @@ -use mina_mesh::models::{AccountBalanceRequest, AccountIdentifier, NetworkIdentifier, PartialBlockIdentifier}; +use mina_mesh::models::{AccountBalanceRequest, AccountIdentifier, PartialBlockIdentifier}; -use super::CompareGroup; +use super::{network_id, CompareGroup}; -#[allow(dead_code)] pub fn account_balance<'a>() -> CompareGroup<'a> { ("/account/balance", vec![ + // historical lookups Box::new(AccountBalanceRequest { account_identifier: Box::new(AccountIdentifier::new( "B62qmo4nfFemr9hFtvz8F5h4JFSCxikVNsUJmZcfXQ9SGJ4abEC1RtH".to_string(), )), block_identifier: Some(Box::new(PartialBlockIdentifier { index: Some(100), hash: None })), - network_identifier: Box::new(NetworkIdentifier::new("mina".to_string(), "devnet".to_string())), + network_identifier: Box::new(network_id()), currencies: None, }), Box::new(AccountBalanceRequest { @@ -21,11 +21,19 @@ pub fn account_balance<'a>() -> CompareGroup<'a> { }), block_identifier: Some(Box::new(PartialBlockIdentifier { index: Some(6265), hash: None })), currencies: None, - network_identifier: Box::new(NetworkIdentifier { - blockchain: "mina".into(), - network: "devnet".into(), - sub_network_identifier: None, - }), + network_identifier: Box::new(network_id()), }), + // current lookups + Box::new(AccountBalanceRequest::new( + network_id(), + AccountIdentifier::new("B62qkYHGYmws5CYa3phYEKoZvrENTegEhUJYMhzHUQe5UZwCdWob8zv".to_string()), + )), ]) } + +pub fn account_balance_not_exists<'a>() -> CompareGroup<'a> { + ("/account/balance", vec![Box::new(AccountBalanceRequest::new( + network_id(), + AccountIdentifier::new("B62qiW9Qwv9UnKfNKdBm6hRLNDobv46rVhX1trGdB35YCNT33CSCVt5".to_string()), + ))]) +} diff --git a/tests/fixtures/block.rs b/tests/fixtures/block.rs index 623500f..16e4d53 100644 --- a/tests/fixtures/block.rs +++ b/tests/fixtures/block.rs @@ -1,16 +1,16 @@ -use mina_mesh::models::{BlockRequest, NetworkIdentifier, PartialBlockIdentifier}; +use mina_mesh::models::{BlockRequest, PartialBlockIdentifier}; -use super::CompareGroup; +use super::{network_id, CompareGroup}; #[allow(dead_code)] pub fn block<'a>() -> CompareGroup<'a> { ("/block", vec![ Box::new(BlockRequest { - network_identifier: Box::new(NetworkIdentifier::new("mina".to_string(), "devnet".to_string())), + network_identifier: Box::new(network_id()), block_identifier: Box::new(PartialBlockIdentifier::new()), }), Box::new(BlockRequest { - network_identifier: Box::new(NetworkIdentifier::new("mina".to_string(), "devnet".to_string())), + network_identifier: Box::new(network_id()), block_identifier: Box::new(PartialBlockIdentifier { index: Some(52676), hash: None }), }), ]) diff --git a/tests/fixtures/mempool.rs b/tests/fixtures/mempool.rs index dac66cf..48fb1e6 100644 --- a/tests/fixtures/mempool.rs +++ b/tests/fixtures/mempool.rs @@ -1,6 +1,6 @@ -use mina_mesh::models::{MempoolTransactionRequest, NetworkIdentifier, NetworkRequest, TransactionIdentifier}; +use mina_mesh::models::{MempoolTransactionRequest, NetworkRequest, TransactionIdentifier}; -use super::CompareGroup; +use super::{network_id, CompareGroup}; pub fn mempool<'a>() -> CompareGroup<'a> { ("/mempool", vec![Box::new(NetworkRequest::new(network_id()))]) @@ -12,7 +12,3 @@ pub fn mempool_transaction<'a>() -> CompareGroup<'a> { TransactionIdentifier::new("hash_not_exists".to_string()), ))]) } - -fn network_id() -> NetworkIdentifier { - NetworkIdentifier::new("mina".to_string(), "devnet".to_string()) -} diff --git a/tests/fixtures/mod.rs b/tests/fixtures/mod.rs index a71c9c7..350eff8 100644 --- a/tests/fixtures/mod.rs +++ b/tests/fixtures/mod.rs @@ -6,12 +6,20 @@ mod mempool; mod network; mod search_transactions; -#[allow(unused_imports)] pub use account_balance::*; #[allow(unused_imports)] pub use block::*; pub use mempool::*; +use mina_mesh::models::{NetworkIdentifier, NetworkRequest}; pub use network::*; pub use search_transactions::*; pub type CompareGroup<'a> = (&'a str, Vec>); + +pub fn network_id() -> NetworkIdentifier { + NetworkIdentifier::new("mina".to_string(), "devnet".to_string()) +} + +pub fn network_request() -> NetworkRequest { + NetworkRequest::new(network_id()) +} diff --git a/tests/fixtures/network.rs b/tests/fixtures/network.rs index 5acd123..3698dfd 100644 --- a/tests/fixtures/network.rs +++ b/tests/fixtures/network.rs @@ -1,7 +1,6 @@ -use mina_mesh::models::{NetworkIdentifier, NetworkRequest}; use serde::{ser::SerializeStruct, Serialize, Serializer}; -use super::CompareGroup; +use super::{network_request, CompareGroup}; struct EmptyPayload; @@ -27,10 +26,6 @@ pub fn network_status<'a>() -> CompareGroup<'a> { ("/network/status", vec![Box::new(network_request())]) } -fn network_request() -> NetworkRequest { - NetworkRequest::new(NetworkIdentifier::new("mina".to_string(), "devnet".to_string())) -} - #[cfg(test)] mod tests { diff --git a/tests/snapshots/account_balance__responses.snap b/tests/snapshots/account_balance__responses.snap index 997d769..9db02c9 100644 --- a/tests/snapshots/account_balance__responses.snap +++ b/tests/snapshots/account_balance__responses.snap @@ -18,14 +18,19 @@ expression: results }, metadata: Some( Object { - "liquid_balance": String("7000000000"), - "locked_balance": String("0"), - "total_balance": String("7000000000"), + "liquid_balance": Number(7000000000), + "locked_balance": Number(0), + "total_balance": Number(7000000000), }, ), }, ], - metadata: None, + metadata: Some( + Object { + "created_via_historical_lookup": Bool(true), + "nonce": String("0"), + }, + ), }, AccountBalanceResponse { block_identifier: BlockIdentifier { @@ -42,13 +47,18 @@ expression: results }, metadata: Some( Object { - "liquid_balance": String("17899903513284"), - "locked_balance": String("0"), - "total_balance": String("17899903513284"), + "liquid_balance": Number(17899903513284), + "locked_balance": Number(0), + "total_balance": Number(17899903513284), }, ), }, ], - metadata: None, + metadata: Some( + Object { + "created_via_historical_lookup": Bool(true), + "nonce": String("27903"), + }, + ), }, ]