Skip to content

Commit d804d77

Browse files
committed
Add token transactions endpoint for the API Server
1 parent 682e03f commit d804d77

File tree

13 files changed

+889
-43
lines changed

13 files changed

+889
-43
lines changed

api-server/api-server-common/src/storage/impls/in_memory/mod.rs

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use crate::storage::storage_api::{
1919
block_aux_data::{BlockAuxData, BlockWithExtraData},
2020
AmountWithDecimals, ApiServerStorageError, BlockInfo, CoinOrTokenStatistic, Delegation,
2121
FungibleTokenData, LockedUtxo, NftWithOwner, Order, PoolBlockStats, PoolDataWithExtraInfo,
22-
TransactionInfo, TransactionWithBlockInfo, Utxo, UtxoLock, UtxoWithExtraInfo,
22+
TokenTransaction, TransactionInfo, TransactionWithBlockInfo, Utxo, UtxoLock, UtxoWithExtraInfo,
2323
};
2424
use common::{
2525
address::Address,
@@ -48,6 +48,7 @@ struct ApiServerInMemoryStorage {
4848
address_balance_table: BTreeMap<String, BTreeMap<CoinOrTokenId, BTreeMap<BlockHeight, Amount>>>,
4949
address_locked_balance_table: BTreeMap<String, BTreeMap<(CoinOrTokenId, BlockHeight), Amount>>,
5050
address_transactions_table: BTreeMap<String, BTreeMap<BlockHeight, Vec<Id<Transaction>>>>,
51+
token_transactions_table: BTreeMap<TokenId, BTreeMap<BlockHeight, Vec<TokenTransaction>>>,
5152
delegation_table: BTreeMap<DelegationId, BTreeMap<BlockHeight, Delegation>>,
5253
main_chain_blocks_table: BTreeMap<BlockHeight, Id<Block>>,
5354
pool_data_table: BTreeMap<PoolId, BTreeMap<BlockHeight, PoolDataWithExtraInfo>>,
@@ -75,6 +76,7 @@ impl ApiServerInMemoryStorage {
7576
address_balance_table: BTreeMap::new(),
7677
address_locked_balance_table: BTreeMap::new(),
7778
address_transactions_table: BTreeMap::new(),
79+
token_transactions_table: BTreeMap::new(),
7880
delegation_table: BTreeMap::new(),
7981
main_chain_blocks_table: BTreeMap::new(),
8082
pool_data_table: BTreeMap::new(),
@@ -173,6 +175,27 @@ impl ApiServerInMemoryStorage {
173175
}))
174176
}
175177

178+
fn get_token_transactions(
179+
&self,
180+
token_id: TokenId,
181+
len: u32,
182+
global_tx_index: i64,
183+
) -> Result<Vec<TokenTransaction>, ApiServerStorageError> {
184+
Ok(self
185+
.token_transactions_table
186+
.get(&token_id)
187+
.map_or_else(Vec::new, |transactions| {
188+
transactions
189+
.iter()
190+
.rev()
191+
.flat_map(|(_, txs)| txs.iter())
192+
.cloned()
193+
.flat_map(|tx| (tx.global_tx_index < global_tx_index).then_some(tx))
194+
.take(len as usize)
195+
.collect()
196+
}))
197+
}
198+
176199
fn get_block(&self, block_id: Id<Block>) -> Result<Option<BlockInfo>, ApiServerStorageError> {
177200
let block_result = self.block_table.get(&block_id);
178201
let block = match block_result {
@@ -864,6 +887,20 @@ impl ApiServerInMemoryStorage {
864887
Ok(())
865888
}
866889

890+
fn del_token_transactions_above_height(
891+
&mut self,
892+
block_height: BlockHeight,
893+
) -> Result<(), ApiServerStorageError> {
894+
// Inefficient, but acceptable for testing with InMemoryStorage
895+
896+
self.token_transactions_table.retain(|_, v| {
897+
v.retain(|k, _| k <= &block_height);
898+
!v.is_empty()
899+
});
900+
901+
Ok(())
902+
}
903+
867904
fn set_address_balance_at_height(
868905
&mut self,
869906
address: &Address<Destination>,
@@ -942,6 +979,36 @@ impl ApiServerInMemoryStorage {
942979
Ok(())
943980
}
944981

982+
fn set_token_transactions_at_height(
983+
&mut self,
984+
token_id: TokenId,
985+
transaction_ids: BTreeSet<Id<Transaction>>,
986+
block_height: BlockHeight,
987+
) -> Result<(), ApiServerStorageError> {
988+
let next_tx_idx = self.token_transactions_table.get(&token_id).map_or(1, |tx| {
989+
tx.values()
990+
.last()
991+
.expect("not empty")
992+
.last()
993+
.expect("not empty")
994+
.global_tx_index
995+
+ 1
996+
});
997+
self.token_transactions_table.entry(token_id).or_default().insert(
998+
block_height,
999+
transaction_ids
1000+
.into_iter()
1001+
.enumerate()
1002+
.map(|(idx, tx_id)| TokenTransaction {
1003+
global_tx_index: next_tx_idx + idx as i64,
1004+
tx_id,
1005+
})
1006+
.collect(),
1007+
);
1008+
1009+
Ok(())
1010+
}
1011+
9451012
fn set_mainchain_block(
9461013
&mut self,
9471014
block_id: Id<Block>,

api-server/api-server-common/src/storage/impls/in_memory/transactional/read.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ use common::{
2626
use crate::storage::storage_api::{
2727
block_aux_data::BlockAuxData, AmountWithDecimals, ApiServerStorageError, ApiServerStorageRead,
2828
BlockInfo, CoinOrTokenStatistic, Delegation, FungibleTokenData, NftWithOwner, Order,
29-
PoolBlockStats, PoolDataWithExtraInfo, TransactionInfo, TransactionWithBlockInfo, Utxo,
30-
UtxoWithExtraInfo,
29+
PoolBlockStats, PoolDataWithExtraInfo, TokenTransaction, TransactionInfo,
30+
TransactionWithBlockInfo, Utxo, UtxoWithExtraInfo,
3131
};
3232

3333
use super::ApiServerInMemoryStorageTransactionalRo;
@@ -68,6 +68,15 @@ impl ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRo<'_> {
6868
self.transaction.get_address_transactions(address)
6969
}
7070

71+
async fn get_token_transactions(
72+
&self,
73+
token_id: TokenId,
74+
len: u32,
75+
global_tx_index: i64,
76+
) -> Result<Vec<TokenTransaction>, ApiServerStorageError> {
77+
self.transaction.get_token_transactions(token_id, len, global_tx_index)
78+
}
79+
7180
async fn get_block(
7281
&self,
7382
block_id: Id<Block>,

api-server/api-server-common/src/storage/impls/in_memory/transactional/write.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ use crate::storage::storage_api::{
1919
block_aux_data::{BlockAuxData, BlockWithExtraData},
2020
AmountWithDecimals, ApiServerStorageError, ApiServerStorageRead, ApiServerStorageWrite,
2121
BlockInfo, CoinOrTokenStatistic, Delegation, FungibleTokenData, LockedUtxo, NftWithOwner,
22-
Order, PoolBlockStats, PoolDataWithExtraInfo, TransactionInfo, TransactionWithBlockInfo, Utxo,
23-
UtxoWithExtraInfo,
22+
Order, PoolBlockStats, PoolDataWithExtraInfo, TokenTransaction, TransactionInfo,
23+
TransactionWithBlockInfo, Utxo, UtxoWithExtraInfo,
2424
};
2525
use common::{
2626
address::Address,
@@ -64,6 +64,13 @@ impl ApiServerStorageWrite for ApiServerInMemoryStorageTransactionalRw<'_> {
6464
self.transaction.del_address_transactions_above_height(block_height)
6565
}
6666

67+
async fn del_token_transactions_above_height(
68+
&mut self,
69+
block_height: BlockHeight,
70+
) -> Result<(), ApiServerStorageError> {
71+
self.transaction.del_token_transactions_above_height(block_height)
72+
}
73+
6774
async fn set_address_balance_at_height(
6875
&mut self,
6976
address: &Address<Destination>,
@@ -104,6 +111,16 @@ impl ApiServerStorageWrite for ApiServerInMemoryStorageTransactionalRw<'_> {
104111
.set_address_transactions_at_height(address, transactions, block_height)
105112
}
106113

114+
async fn set_token_transactions_at_height(
115+
&mut self,
116+
token_id: TokenId,
117+
transactions: BTreeSet<Id<Transaction>>,
118+
block_height: BlockHeight,
119+
) -> Result<(), ApiServerStorageError> {
120+
self.transaction
121+
.set_token_transactions_at_height(token_id, transactions, block_height)
122+
}
123+
107124
async fn set_mainchain_block(
108125
&mut self,
109126
block_id: Id<Block>,
@@ -331,6 +348,15 @@ impl ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRw<'_> {
331348
self.transaction.get_address_transactions(address)
332349
}
333350

351+
async fn get_token_transactions(
352+
&self,
353+
token_id: TokenId,
354+
len: u32,
355+
global_tx_index: i64,
356+
) -> Result<Vec<TokenTransaction>, ApiServerStorageError> {
357+
self.transaction.get_token_transactions(token_id, len, global_tx_index)
358+
}
359+
334360
async fn get_latest_blocktimestamps(
335361
&self,
336362
) -> Result<Vec<BlockTimestamp>, ApiServerStorageError> {

api-server/api-server-common/src/storage/impls/postgres/queries.rs

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ use crate::storage::{
3939
block_aux_data::{BlockAuxData, BlockWithExtraData},
4040
AmountWithDecimals, ApiServerStorageError, BlockInfo, CoinOrTokenStatistic, Delegation,
4141
FungibleTokenData, LockedUtxo, NftWithOwner, Order, PoolBlockStats, PoolDataWithExtraInfo,
42-
TransactionInfo, TransactionWithBlockInfo, Utxo, UtxoWithExtraInfo,
42+
TokenTransaction, TransactionInfo, TransactionWithBlockInfo, Utxo, UtxoWithExtraInfo,
4343
},
4444
};
4545

@@ -495,6 +495,49 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
495495
Ok(transaction_ids)
496496
}
497497

498+
pub async fn get_token_transactions(
499+
&self,
500+
token_id: TokenId,
501+
len: u32,
502+
global_tx_index: i64,
503+
) -> Result<Vec<TokenTransaction>, ApiServerStorageError> {
504+
let len = len as i64;
505+
let rows = self
506+
.tx
507+
.query(
508+
r#"
509+
SELECT global_tx_index, transaction_id
510+
FROM ml.token_transactions
511+
WHERE token_id = $1 AND global_tx_index < $2
512+
ORDER BY global_tx_index DESC
513+
LIMIT $3;
514+
"#,
515+
&[&token_id.encode(), &global_tx_index, &len],
516+
)
517+
.await
518+
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?;
519+
520+
let mut transactions = Vec::with_capacity(rows.len());
521+
522+
for row in &rows {
523+
let global_tx_index: i64 = row.get(0);
524+
let tx_id: Vec<u8> = row.get(1);
525+
let tx_id = Id::<Transaction>::decode_all(&mut tx_id.as_slice()).map_err(|e| {
526+
ApiServerStorageError::DeserializationError(format!(
527+
"Transaction id deserialization failed: {}",
528+
e
529+
))
530+
})?;
531+
532+
transactions.push(TokenTransaction {
533+
global_tx_index,
534+
tx_id,
535+
});
536+
}
537+
538+
Ok(transactions)
539+
}
540+
498541
pub async fn del_address_transactions_above_height(
499542
&mut self,
500543
block_height: BlockHeight,
@@ -512,6 +555,23 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
512555
Ok(())
513556
}
514557

558+
pub async fn del_token_transactions_above_height(
559+
&mut self,
560+
block_height: BlockHeight,
561+
) -> Result<(), ApiServerStorageError> {
562+
let height = Self::block_height_to_postgres_friendly(block_height);
563+
564+
self.tx
565+
.execute(
566+
"DELETE FROM ml.token_transactions WHERE block_height > $1;",
567+
&[&height],
568+
)
569+
.await
570+
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?;
571+
572+
Ok(())
573+
}
574+
515575
pub async fn set_address_transactions_at_height(
516576
&mut self,
517577
address: &str,
@@ -538,6 +598,32 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
538598
Ok(())
539599
}
540600

601+
pub async fn set_token_transactions_at_height(
602+
&mut self,
603+
token_id: TokenId,
604+
transaction_ids: BTreeSet<Id<Transaction>>,
605+
block_height: BlockHeight,
606+
) -> Result<(), ApiServerStorageError> {
607+
let height = Self::block_height_to_postgres_friendly(block_height);
608+
609+
for transaction_id in transaction_ids {
610+
self.tx
611+
.execute(
612+
r#"
613+
INSERT INTO ml.token_transactions (token_id, block_height, transaction_id)
614+
VALUES ($1, $2, $3)
615+
ON CONFLICT (token_id, block_height, transaction_id)
616+
DO NOTHING;
617+
"#,
618+
&[&token_id.encode(), &height, &transaction_id.encode()],
619+
)
620+
.await
621+
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?;
622+
}
623+
624+
Ok(())
625+
}
626+
541627
pub async fn get_latest_blocktimestamps(
542628
&self,
543629
) -> Result<Vec<BlockTimestamp>, ApiServerStorageError> {
@@ -732,6 +818,20 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
732818
)
733819
.await?;
734820

821+
self.just_execute(
822+
"CREATE TABLE ml.token_transactions (
823+
global_tx_index bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
824+
token_id bytea NOT NULL,
825+
block_height bigint NOT NULL,
826+
transaction_id bytea NOT NULL,
827+
UNIQUE (token_id, block_height, transaction_id)
828+
);",
829+
)
830+
.await?;
831+
832+
self.just_execute("CREATE INDEX token_transactions_global_tx_index ON ml.token_transactions (token_id, global_tx_index DESC);")
833+
.await?;
834+
735835
self.just_execute(
736836
"CREATE TABLE ml.utxo (
737837
outpoint bytea NOT NULL,

api-server/api-server-common/src/storage/impls/postgres/transactional/read.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ use crate::storage::{
2626
storage_api::{
2727
block_aux_data::BlockAuxData, AmountWithDecimals, ApiServerStorageError,
2828
ApiServerStorageRead, BlockInfo, CoinOrTokenStatistic, Delegation, FungibleTokenData,
29-
NftWithOwner, Order, PoolBlockStats, PoolDataWithExtraInfo, TransactionInfo,
30-
TransactionWithBlockInfo, Utxo, UtxoWithExtraInfo,
29+
NftWithOwner, Order, PoolBlockStats, PoolDataWithExtraInfo, TokenTransaction,
30+
TransactionInfo, TransactionWithBlockInfo, Utxo, UtxoWithExtraInfo,
3131
},
3232
};
3333
use std::collections::BTreeMap;
@@ -94,6 +94,18 @@ impl ApiServerStorageRead for ApiServerPostgresTransactionalRo<'_> {
9494
Ok(res)
9595
}
9696

97+
async fn get_token_transactions(
98+
&self,
99+
token_id: TokenId,
100+
len: u32,
101+
global_tx_index: i64,
102+
) -> Result<Vec<TokenTransaction>, ApiServerStorageError> {
103+
let conn = QueryFromConnection::new(self.connection.as_ref().expect(CONN_ERR));
104+
let res = conn.get_token_transactions(token_id, len, global_tx_index).await?;
105+
106+
Ok(res)
107+
}
108+
97109
async fn get_latest_blocktimestamps(
98110
&self,
99111
) -> Result<Vec<BlockTimestamp>, ApiServerStorageError> {

0 commit comments

Comments
 (0)