Skip to content

Commit 4b5579d

Browse files
committed
feat: address utxos and transactions endpoints
Signed-off-by: William Hankins <[email protected]>
1 parent df09d6f commit 4b5579d

File tree

7 files changed

+347
-40
lines changed

7 files changed

+347
-40
lines changed

common/src/queries/blocks.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ pub enum BlocksStateQuery {
7272
GetUTxOHashes {
7373
utxo_ids: Vec<UTxOIdentifier>,
7474
},
75+
GetTransactionHashesAndTimestamps {
76+
tx_ids: Vec<TxIdentifier>,
77+
},
7578
}
7679

7780
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -96,6 +99,7 @@ pub enum BlocksStateQueryResponse {
9699
BlockHashes(BlockHashes),
97100
TransactionHashes(TransactionHashes),
98101
UTxOHashes(UTxOHashes),
102+
TransactionHashesAndTimestamps(TransactionHashesAndTimeStamps),
99103
Error(QueryError),
100104
}
101105

@@ -240,3 +244,9 @@ pub struct UTxOHashes {
240244
pub block_hashes: Vec<BlockHash>,
241245
pub tx_hashes: Vec<TxHash>,
242246
}
247+
248+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
249+
pub struct TransactionHashesAndTimeStamps {
250+
pub tx_hashes: Vec<TxHash>,
251+
pub timestamps: Vec<u64>,
252+
}

modules/address_state/src/address_state.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,11 @@ impl AddressState {
209209
match state.get_address_utxos(address).await {
210210
Ok(Some(utxos)) => AddressStateQueryResponse::AddressUTxOs(utxos),
211211
Ok(None) => match address.to_string() {
212-
Ok(addr_str) => AddressStateQueryResponse::Error(
213-
QueryError::not_found(format!("Address {}", addr_str)),
214-
),
212+
Ok(addr_str) => {
213+
AddressStateQueryResponse::Error(QueryError::not_found(
214+
format!("Address {} not found", addr_str),
215+
))
216+
}
215217
Err(e) => {
216218
AddressStateQueryResponse::Error(QueryError::internal_error(
217219
format!("Could not convert address to string: {}", e),
@@ -227,9 +229,11 @@ impl AddressState {
227229
match state.get_address_transactions(address).await {
228230
Ok(Some(txs)) => AddressStateQueryResponse::AddressTransactions(txs),
229231
Ok(None) => match address.to_string() {
230-
Ok(addr_str) => AddressStateQueryResponse::Error(
231-
QueryError::not_found(format!("Address {}", addr_str)),
232-
),
232+
Ok(addr_str) => {
233+
AddressStateQueryResponse::Error(QueryError::not_found(
234+
format!("Address {} not found", addr_str),
235+
))
236+
}
233237
Err(e) => {
234238
AddressStateQueryResponse::Error(QueryError::internal_error(
235239
format!("Could not convert address to string: {}", e),

modules/address_state/src/immutable_address_store.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ pub struct ImmutableAddressStore {
3131

3232
impl ImmutableAddressStore {
3333
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
34-
let cfg = fjall::Config::new(path).max_write_buffer_size(512 * 1024 * 1024).temporary(true);
34+
let path = path.as_ref();
35+
if path.exists() {
36+
std::fs::remove_dir_all(path)?;
37+
}
38+
let cfg = fjall::Config::new(path).max_write_buffer_size(512 * 1024 * 1024);
3539
let keyspace = Keyspace::open(cfg)?;
3640

3741
let utxos = keyspace.open_partition("address_utxos", PartitionCreateOptions::default())?;

modules/chain_store/src/chain_store.rs

Lines changed: 120 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod stores;
22

33
use crate::stores::{fjall::FjallStore, Block, Store};
44
use acropolis_codec::{block::map_to_block_issuer, map_parameters};
5+
use acropolis_common::queries::blocks::TransactionHashesAndTimeStamps;
56
use acropolis_common::queries::errors::QueryError;
67
use acropolis_common::{
78
crypto::keyhash_224,
@@ -330,27 +331,133 @@ impl ChainStore {
330331
let mut block_hashes = Vec::with_capacity(utxo_ids.len());
331332

332333
for utxo in utxo_ids {
333-
if let Ok(Some(block)) = store.get_block_by_number(utxo.block_number().into()) {
334-
if let Ok(hash) = Self::get_block_hash(&block) {
335-
if let Ok(tx_hashes_in_block) =
336-
Self::to_block_transaction_hashes(&block)
337-
{
338-
if let Some(tx_hash) =
339-
tx_hashes_in_block.get(utxo.tx_index() as usize)
340-
{
341-
tx_hashes.push(*tx_hash);
342-
block_hashes.push(hash);
343-
}
344-
}
334+
let block = match store.get_block_by_number(utxo.block_number().into()) {
335+
Ok(Some(b)) => b,
336+
Ok(None) => {
337+
return Ok(BlocksStateQueryResponse::Error(QueryError::not_found(
338+
format!("Block {} not found", utxo.block_number()),
339+
)))
345340
}
346-
}
341+
Err(e) => {
342+
return Ok(BlocksStateQueryResponse::Error(QueryError::not_found(
343+
format!("Failed to fetch block {}: {e}", utxo.block_number()),
344+
)))
345+
}
346+
};
347+
348+
let block_hash = match Self::get_block_hash(&block) {
349+
Ok(h) => h,
350+
Err(e) => {
351+
return Ok(BlocksStateQueryResponse::Error(QueryError::not_found(
352+
format!(
353+
"Failed to extract block hash for block {}: {e}",
354+
utxo.block_number()
355+
),
356+
)))
357+
}
358+
};
359+
360+
let tx_hashes_in_block = match Self::to_block_transaction_hashes(&block) {
361+
Ok(h) => h,
362+
Err(e) => {
363+
return Ok(BlocksStateQueryResponse::Error(QueryError::not_found(
364+
format!(
365+
"Failed to extract tx list for block {}: {e}",
366+
utxo.block_number()
367+
),
368+
)))
369+
}
370+
};
371+
372+
let tx_hash = match tx_hashes_in_block.get(utxo.tx_index() as usize) {
373+
Some(h) => h,
374+
None => {
375+
return Ok(BlocksStateQueryResponse::Error(QueryError::not_found(
376+
format!(
377+
"tx_index {} out of bounds for block {}",
378+
utxo.tx_index(),
379+
utxo.block_number()
380+
),
381+
)))
382+
}
383+
};
384+
385+
tx_hashes.push(*tx_hash);
386+
block_hashes.push(block_hash);
347387
}
348388

349389
Ok(BlocksStateQueryResponse::UTxOHashes(UTxOHashes {
350390
block_hashes,
351391
tx_hashes,
352392
}))
353393
}
394+
BlocksStateQuery::GetTransactionHashesAndTimestamps { tx_ids } => {
395+
let mut tx_hashes = Vec::with_capacity(tx_ids.len());
396+
let mut timestamps = Vec::with_capacity(tx_ids.len());
397+
398+
for tx in tx_ids {
399+
let block = match store.get_block_by_number(tx.block_number().into()) {
400+
Ok(Some(b)) => b,
401+
Ok(None) => {
402+
return Ok(BlocksStateQueryResponse::Error(QueryError::not_found(
403+
format!("Block {} not found", tx.block_number()),
404+
)))
405+
}
406+
Err(e) => {
407+
return Ok(BlocksStateQueryResponse::Error(QueryError::not_found(
408+
format!("Failed to fetch block {}: {e}", tx.block_number()),
409+
)))
410+
}
411+
};
412+
413+
let hashes_in_block = match Self::to_block_transaction_hashes(&block) {
414+
Ok(h) => h,
415+
Err(e) => {
416+
return Ok(BlocksStateQueryResponse::Error(QueryError::not_found(
417+
format!(
418+
"Failed to extract tx hashes for block {}: {e}",
419+
tx.block_number()
420+
),
421+
)))
422+
}
423+
};
424+
425+
let tx_hash = match hashes_in_block.get(tx.tx_index() as usize) {
426+
Some(h) => h,
427+
None => {
428+
return Ok(BlocksStateQueryResponse::Error(QueryError::not_found(
429+
format!(
430+
"tx_index {} out of bounds for block {}",
431+
tx.tx_index(),
432+
tx.block_number()
433+
),
434+
)))
435+
}
436+
};
437+
438+
let block_info = match Self::to_block_info(block, store, state, false) {
439+
Ok(info) => info,
440+
Err(e) => {
441+
return Ok(BlocksStateQueryResponse::Error(QueryError::not_found(
442+
format!(
443+
"Failed to build block info for block {}: {e}",
444+
tx.block_number()
445+
),
446+
)))
447+
}
448+
};
449+
450+
tx_hashes.push(*tx_hash);
451+
timestamps.push(block_info.timestamp);
452+
}
453+
454+
Ok(BlocksStateQueryResponse::TransactionHashesAndTimestamps(
455+
TransactionHashesAndTimeStamps {
456+
tx_hashes,
457+
timestamps,
458+
},
459+
))
460+
}
354461
}
355462
}
356463

modules/rest_blockfrost/src/handlers/accounts.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::sync::Arc;
33

44
use crate::handlers_config::HandlersConfig;
55
use crate::types::{
6-
AccountAddressREST, AccountRewardREST, AccountTotalsREST, AccountUTxOREST,
7-
AccountWithdrawalREST, AmountList, DelegationUpdateREST, RegistrationUpdateREST,
6+
AccountAddressREST, AccountRewardREST, AccountTotalsREST, AccountWithdrawalREST, AmountList,
7+
DelegationUpdateREST, RegistrationUpdateREST, UTxOREST,
88
};
99
use acropolis_common::messages::{Message, RESTResponse, StateQuery, StateQueryResponse};
1010
use acropolis_common::queries::accounts::{AccountsStateQuery, AccountsStateQueryResponse};
@@ -784,13 +784,13 @@ pub async fn handle_account_utxos_blockfrost(
784784
msg,
785785
|message| match message {
786786
Message::StateQueryResponse(StateQueryResponse::Blocks(
787-
BlocksStateQueryResponse::UTxOHashes(utxos),
788-
)) => Ok(utxos),
787+
BlocksStateQueryResponse::UTxOHashes(hashes),
788+
)) => Ok(hashes),
789789
Message::StateQueryResponse(StateQueryResponse::Blocks(
790790
BlocksStateQueryResponse::Error(e),
791791
)) => Err(e),
792792
_ => Err(QueryError::internal_error(
793-
"Unexpected message type while retrieving account UTxOs",
793+
"Unexpected message type while retrieving UTxO hashes",
794794
)),
795795
},
796796
)
@@ -846,7 +846,7 @@ pub async fn handle_account_utxos_blockfrost(
846846
None => None,
847847
};
848848

849-
rest_response.push(AccountUTxOREST {
849+
rest_response.push(UTxOREST {
850850
address: entry.address.to_string()?,
851851
tx_hash,
852852
output_index,

0 commit comments

Comments
 (0)