Skip to content

Commit f7c3d34

Browse files
committed
feat: implemented account utxos endpoint
Signed-off-by: William Hankins <[email protected]>
1 parent 61368f7 commit f7c3d34

15 files changed

+203
-70
lines changed

common/src/queries/blocks.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::{
22
queries::misc::Order,
33
serialization::{Bech32Conversion, Bech32WithHrp},
4-
Address, BlockHash, GenesisDelegate, HeavyDelegate, KeyHash, TxHash, TxIdentifier, VrfKeyHash,
4+
Address, BlockHash, GenesisDelegate, HeavyDelegate, KeyHash, TxHash, TxIdentifier,
5+
UTxOIdentifier, VrfKeyHash,
56
};
67
use cryptoxide::hashing::blake2b::Blake2b;
78
use serde::ser::{Serialize, SerializeStruct, Serializer};
@@ -67,6 +68,9 @@ pub enum BlocksStateQuery {
6768
GetTransactionHashes {
6869
tx_ids: Vec<TxIdentifier>,
6970
},
71+
GetUTxOHashes {
72+
utxo_ids: Vec<UTxOIdentifier>,
73+
},
7074
}
7175

7276
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -90,6 +94,7 @@ pub enum BlocksStateQueryResponse {
9094
BlockInvolvedAddresses(BlockInvolvedAddresses),
9195
BlockHashes(BlockHashes),
9296
TransactionHashes(TransactionHashes),
97+
UTxOHashes(UTxOHashes),
9398
NotFound,
9499
Error(String),
95100
}
@@ -229,3 +234,9 @@ pub struct BlockHashes {
229234
pub struct TransactionHashes {
230235
pub tx_hashes: HashMap<TxIdentifier, TxHash>,
231236
}
237+
238+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
239+
pub struct UTxOHashes {
240+
pub block_hashes: Vec<BlockHash>,
241+
pub tx_hashes: Vec<TxHash>,
242+
}

common/src/queries/utxos.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{UTxOIdentifier, Value};
1+
use crate::{UTXOValue, UTxOIdentifier, Value};
22

33
pub const DEFAULT_UTXOS_QUERY_TOPIC: (&str, &str) =
44
("utxo-state-query-topic", "cardano.query.utxos");
@@ -8,15 +8,15 @@ pub enum UTxOStateQuery {
88
GetUTxOsSum {
99
utxo_identifiers: Vec<UTxOIdentifier>,
1010
},
11-
GetUTxOsMap {
11+
GetUTxOs {
1212
utxo_identifiers: Vec<UTxOIdentifier>,
1313
},
1414
}
1515

1616
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1717
pub enum UTxOStateQueryResponse {
1818
UTxOsSum(Value),
19-
UTxOsMap(Vec<Value>),
19+
UTxOs(Vec<UTXOValue>),
2020
NotFound,
2121
Error(String),
2222
}

common/src/types.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,22 @@ impl Neg for ValueDelta {
446446
}
447447
}
448448

449+
/// Value stored in UTXO
450+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
451+
pub struct UTXOValue {
452+
/// Address in binary
453+
pub address: Address,
454+
455+
/// Value in Lovelace
456+
pub value: Value,
457+
458+
/// Datum
459+
pub datum: Option<Datum>,
460+
461+
/// Reference script
462+
pub reference_script: Option<ReferenceScript>,
463+
}
464+
449465
/// Transaction output (UTXO)
450466
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
451467
pub struct TxOutput {

modules/chain_store/src/chain_store.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ use acropolis_codec::{block::map_to_block_issuer, map_parameters};
44
use acropolis_common::{
55
crypto::keyhash_224,
66
messages::{CardanoMessage, Message, StateQuery, StateQueryResponse},
7-
queries::blocks::{
8-
BlockHashes, BlockInfo, BlockInvolvedAddress, BlockInvolvedAddresses, BlockKey,
9-
BlockTransaction, BlockTransactions, BlockTransactionsCBOR, BlocksStateQuery,
10-
BlocksStateQueryResponse, NextBlocks, PreviousBlocks, TransactionHashes,
11-
DEFAULT_BLOCKS_QUERY_TOPIC,
7+
queries::{
8+
blocks::{
9+
BlockHashes, BlockInfo, BlockInvolvedAddress, BlockInvolvedAddresses, BlockKey,
10+
BlockTransaction, BlockTransactions, BlockTransactionsCBOR, BlocksStateQuery,
11+
BlocksStateQueryResponse, NextBlocks, PreviousBlocks, TransactionHashes, UTxOHashes,
12+
DEFAULT_BLOCKS_QUERY_TOPIC,
13+
},
14+
misc::Order,
1215
},
13-
queries::misc::Order,
1416
state_history::{StateHistory, StateHistoryStore},
1517
BechOrdAddress, BlockHash, GenesisDelegate, HeavyDelegate, PoolId, TxHash,
1618
};
@@ -295,6 +297,32 @@ impl ChainStore {
295297
TransactionHashes { tx_hashes },
296298
))
297299
}
300+
BlocksStateQuery::GetUTxOHashes { utxo_ids } => {
301+
let mut tx_hashes = Vec::with_capacity(utxo_ids.len());
302+
let mut block_hashes = Vec::with_capacity(utxo_ids.len());
303+
304+
for utxo in utxo_ids {
305+
if let Ok(Some(block)) = store.get_block_by_number(utxo.block_number().into()) {
306+
if let Ok(hash) = Self::get_block_hash(&block) {
307+
if let Ok(tx_hashes_in_block) =
308+
Self::to_block_transaction_hashes(&block)
309+
{
310+
if let Some(tx_hash) =
311+
tx_hashes_in_block.get(utxo.tx_index() as usize)
312+
{
313+
tx_hashes.push(*tx_hash);
314+
block_hashes.push(hash);
315+
}
316+
}
317+
}
318+
}
319+
}
320+
321+
Ok(BlocksStateQueryResponse::UTxOHashes(UTxOHashes {
322+
block_hashes,
323+
tx_hashes,
324+
}))
325+
}
298326
}
299327
}
300328

modules/rest_blockfrost/src/handlers/accounts.rs

Lines changed: 91 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,17 @@ use acropolis_common::queries::blocks::{
88
BlocksStateQuery, BlocksStateQueryResponse, TransactionHashes,
99
};
1010
use acropolis_common::queries::utils::query_state;
11-
use acropolis_common::queries::utxos::UTxOStateQuery;
11+
use acropolis_common::queries::utxos::{UTxOStateQuery, UTxOStateQueryResponse};
1212
use acropolis_common::serialization::{Bech32Conversion, Bech32WithHrp};
13-
use acropolis_common::{DRepChoice, StakeAddress};
13+
use acropolis_common::{DRepChoice, Datum, ReferenceScript, StakeAddress};
1414
use anyhow::{anyhow, Result};
15+
use blake2::{Blake2b512, Digest};
1516
use caryatid_sdk::Context;
1617

1718
use crate::handlers_config::HandlersConfig;
1819
use crate::types::{
19-
AccountAddressREST, AccountRewardREST, AccountWithdrawalREST, DelegationUpdateREST,
20-
RegistrationUpdateREST,
20+
AccountAddressREST, AccountRewardREST, AccountUTxOREST, AccountWithdrawalREST,
21+
DelegationUpdateREST, RegistrationUpdateREST,
2122
};
2223

2324
#[derive(serde::Serialize)]
@@ -833,17 +834,12 @@ pub async fn handle_account_utxos_blockfrost(
833834
)));
834835
let utxo_identifiers = query_state(
835836
&context,
836-
&handlers_config.historical_accounts_query_topic,
837+
&handlers_config.addresses_query_topic,
837838
msg,
838839
|message| match message {
839840
Message::StateQueryResponse(StateQueryResponse::Addresses(
840841
AddressStateQueryResponse::AddressesUTxOs(utxos),
841842
)) => Ok(utxos),
842-
Message::StateQueryResponse(StateQueryResponse::Addresses(
843-
AddressStateQueryResponse::NotFound,
844-
)) => Err(anyhow::anyhow!(
845-
"Internal server error while retrieving account UTxOs: No UTxOs found"
846-
)),
847843
Message::StateQueryResponse(StateQueryResponse::Addresses(
848844
AddressStateQueryResponse::Error(e),
849845
)) => Err(anyhow::anyhow!(
@@ -856,33 +852,104 @@ pub async fn handle_account_utxos_blockfrost(
856852
)
857853
.await?;
858854

855+
// Get TxHashes and BlockHashes from UTxOIdentifiers
856+
let msg = Arc::new(Message::StateQuery(StateQuery::Blocks(
857+
BlocksStateQuery::GetUTxOHashes {
858+
utxo_ids: utxo_identifiers.clone(),
859+
},
860+
)));
861+
862+
let hashes = query_state(
863+
&context,
864+
&handlers_config.blocks_query_topic,
865+
msg,
866+
|message| match message {
867+
Message::StateQueryResponse(StateQueryResponse::Blocks(
868+
BlocksStateQueryResponse::UTxOHashes(utxos),
869+
)) => Ok(utxos),
870+
Message::StateQueryResponse(StateQueryResponse::Blocks(
871+
BlocksStateQueryResponse::Error(e),
872+
)) => Err(anyhow::anyhow!(
873+
"Internal server error while retrieving utxo hashes: {e}"
874+
)),
875+
_ => Err(anyhow::anyhow!(
876+
"Unexpected message type while retrieving account UTxOs"
877+
)),
878+
},
879+
)
880+
.await?;
881+
859882
// Get UTxO balances from utxo state
860883
let msg = Arc::new(Message::StateQuery(StateQuery::UTxOs(
861-
UTxOStateQuery::GetUTxOsMap { utxo_identifiers },
884+
UTxOStateQuery::GetUTxOs {
885+
utxo_identifiers: utxo_identifiers.clone(),
886+
},
862887
)));
863-
let balances = query_state(
888+
let entries = query_state(
864889
&context,
865-
&handlers_config.historical_accounts_query_topic,
890+
&handlers_config.utxos_query_topic,
866891
msg,
867892
|message| match message {
868-
Message::StateQueryResponse(StateQueryResponse::Addresses(
869-
AddressStateQueryResponse::AddressesUTxOs(utxos),
870-
)) => Ok(Some(utxos)),
871-
Message::StateQueryResponse(StateQueryResponse::Addresses(
872-
AddressStateQueryResponse::NotFound,
873-
)) => Ok(None),
874-
Message::StateQueryResponse(StateQueryResponse::Addresses(
875-
AddressStateQueryResponse::Error(e),
893+
Message::StateQueryResponse(StateQueryResponse::UTxOs(
894+
UTxOStateQueryResponse::UTxOs(utxos),
895+
)) => Ok(utxos),
896+
Message::StateQueryResponse(StateQueryResponse::UTxOs(
897+
UTxOStateQueryResponse::Error(e),
876898
)) => Err(anyhow::anyhow!(
877-
"Internal server error while retrieving account UTxOs: {e}"
899+
"Internal server error while retrieving UTxO entries: {e}"
878900
)),
879901
_ => Err(anyhow::anyhow!(
880-
"Unexpected message type while retrieving account UTxOs"
902+
"Unexpected message type while retrieving UTxO entries"
881903
)),
882904
},
883905
)
884906
.await?;
885-
Ok(RESTResponse::with_text(501, "Not implemented"))
907+
908+
let mut rest_response = Vec::with_capacity(entries.len());
909+
for (i, entry) in entries.into_iter().enumerate() {
910+
let tx_hash = hashes.tx_hashes.get(i).map(hex::encode).unwrap_or_default();
911+
let block_hash = hashes.block_hashes.get(i).map(hex::encode).unwrap_or_default();
912+
let output_index = utxo_identifiers.get(i).map(|id| id.output_index()).unwrap_or(0);
913+
let (data_hash, inline_datum) = match &entry.datum {
914+
Some(Datum::Hash(h)) => (Some(hex::encode(h)), None),
915+
Some(Datum::Inline(bytes)) => (None, Some(hex::encode(bytes))),
916+
None => (None, None),
917+
};
918+
let reference_script_hash = match &entry.reference_script {
919+
Some(script) => {
920+
let bytes = match script {
921+
ReferenceScript::Native(b)
922+
| ReferenceScript::PlutusV1(b)
923+
| ReferenceScript::PlutusV2(b)
924+
| ReferenceScript::PlutusV3(b) => b,
925+
};
926+
let mut hasher = Blake2b512::new();
927+
hasher.update(bytes);
928+
let result = hasher.finalize();
929+
Some(hex::encode(&result[..32]))
930+
}
931+
None => None,
932+
};
933+
934+
rest_response.push(AccountUTxOREST {
935+
address: entry.address.to_string()?,
936+
tx_hash,
937+
output_index,
938+
amount: entry.value.into(),
939+
block: block_hash,
940+
data_hash,
941+
inline_datum,
942+
reference_script_hash,
943+
})
944+
}
945+
946+
match serde_json::to_string_pretty(&rest_response) {
947+
Ok(json) => Ok(RESTResponse::with_json(200, &json)),
948+
Err(e) => Ok(RESTResponse::with_text(
949+
500,
950+
&format!("Internal server error while serializing addresses: {e}"),
951+
)),
952+
}
886953
}
887954

888955
fn parse_stake_address(params: &[String]) -> Result<StakeAddress, RESTResponse> {

modules/rest_blockfrost/src/types.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,3 +899,15 @@ impl TryFrom<&AccountReward> for AccountRewardREST {
899899
pub struct AccountAddressREST {
900900
pub address: String,
901901
}
902+
903+
#[derive(serde::Serialize)]
904+
pub struct AccountUTxOREST {
905+
pub address: String,
906+
pub tx_hash: String,
907+
pub output_index: u16,
908+
pub amount: AmountList,
909+
pub block: String,
910+
pub data_hash: Option<String>,
911+
pub inline_datum: Option<String>,
912+
pub reference_script_hash: Option<String>,
913+
}

modules/utxo_state/src/dashmap_immutable_utxo_store.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// Faster and API is simpler because it uses internally sharded locks
33
// but it takes a lot more memory than HashMap
44

5-
use crate::state::{ImmutableUTXOStore, UTXOValue};
6-
use acropolis_common::UTxOIdentifier;
5+
use crate::state::ImmutableUTXOStore;
6+
use acropolis_common::{UTXOValue, UTxOIdentifier};
77
use anyhow::Result;
88
use async_trait::async_trait;
99
use config::Config;

modules/utxo_state/src/fake_immutable_utxo_store.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Fake store for immutable UTXOs
22
3-
use crate::state::{ImmutableUTXOStore, UTXOValue};
4-
use acropolis_common::{Address, UTxOIdentifier, Value};
3+
use crate::state::ImmutableUTXOStore;
4+
use acropolis_common::{Address, UTXOValue, UTxOIdentifier, Value};
55
use anyhow::Result;
66
use async_trait::async_trait;
77
use config::Config;

modules/utxo_state/src/fjall_async_immutable_utxo_store.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! On-disk store using Fjall for immutable UTXOs
22
3-
use crate::state::{ImmutableUTXOStore, UTXOValue};
4-
use acropolis_common::UTxOIdentifier;
3+
use crate::state::ImmutableUTXOStore;
4+
use acropolis_common::{UTXOValue, UTxOIdentifier};
55
use anyhow::Result;
66
use async_trait::async_trait;
77
use config::Config;

modules/utxo_state/src/fjall_immutable_utxo_store.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! On-disk store using Fjall for immutable UTXOs
22
3-
use crate::state::{ImmutableUTXOStore, UTXOValue};
4-
use acropolis_common::UTxOIdentifier;
3+
use crate::state::ImmutableUTXOStore;
4+
use acropolis_common::{UTXOValue, UTxOIdentifier};
55
use anyhow::Result;
66
use async_trait::async_trait;
77
use config::Config;

0 commit comments

Comments
 (0)