Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api-server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/

### Added
- New endpoint was added: `/v2/transaction/{id}/output/{idx}`.
- New endpoint was added: `/v2/token/{id}/transactions` will return all transactions related to a token

### Changed
- `/v2/token/ticker/{ticker}` will now return all tokens whose ticker has the specified `{ticker}`
Expand Down
120 changes: 103 additions & 17 deletions api-server/api-server-common/src/storage/impls/in_memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@

pub mod transactional;

use crate::storage::storage_api::{
block_aux_data::{BlockAuxData, BlockWithExtraData},
AmountWithDecimals, ApiServerStorageError, BlockInfo, CoinOrTokenStatistic, Delegation,
FungibleTokenData, LockedUtxo, NftWithOwner, Order, PoolBlockStats, PoolDataWithExtraInfo,
TransactionInfo, TransactionWithBlockInfo, Utxo, UtxoLock, UtxoWithExtraInfo,
use std::{
cmp::{Ordering, Reverse},
collections::{BTreeMap, BTreeSet},
ops::Bound::{Excluded, Unbounded},
sync::Arc,
};

use common::{
address::Address,
chain::{
Expand All @@ -31,23 +32,42 @@ use common::{
},
primitives::{id::WithId, Amount, BlockHeight, CoinOrTokenId, Id, Idable},
};
use itertools::Itertools as _;
use std::{
cmp::Reverse,
collections::{BTreeMap, BTreeSet},
ops::Bound::{Excluded, Unbounded},
sync::Arc,

use crate::storage::storage_api::{
block_aux_data::{BlockAuxData, BlockWithExtraData},
AmountWithDecimals, ApiServerStorageError, BlockInfo, CoinOrTokenStatistic, Delegation,
FungibleTokenData, LockedUtxo, NftWithOwner, Order, PoolBlockStats, PoolDataWithExtraInfo,
TokenTransaction, TransactionInfo, TransactionWithBlockInfo, Utxo, UtxoLock, UtxoWithExtraInfo,
};

use itertools::Itertools as _;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put it after the std imports?


use super::CURRENT_STORAGE_VERSION;

#[derive(Debug, Clone, PartialEq, Eq)]
struct TokenTransactionOrderedByTxId(TokenTransaction);

impl PartialOrd for TokenTransactionOrderedByTxId {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for TokenTransactionOrderedByTxId {
fn cmp(&self, other: &Self) -> Ordering {
self.0.tx_id.cmp(&other.0.tx_id)
}
}

#[derive(Debug, Clone)]
struct ApiServerInMemoryStorage {
block_table: BTreeMap<Id<Block>, BlockWithExtraData>,
block_aux_data_table: BTreeMap<Id<Block>, BlockAuxData>,
address_balance_table: BTreeMap<String, BTreeMap<CoinOrTokenId, BTreeMap<BlockHeight, Amount>>>,
address_locked_balance_table: BTreeMap<String, BTreeMap<(CoinOrTokenId, BlockHeight), Amount>>,
address_transactions_table: BTreeMap<String, BTreeMap<BlockHeight, Vec<Id<Transaction>>>>,
token_transactions_table:
BTreeMap<TokenId, BTreeMap<BlockHeight, BTreeSet<TokenTransactionOrderedByTxId>>>,
delegation_table: BTreeMap<DelegationId, BTreeMap<BlockHeight, Delegation>>,
main_chain_blocks_table: BTreeMap<BlockHeight, Id<Block>>,
pool_data_table: BTreeMap<PoolId, BTreeMap<BlockHeight, PoolDataWithExtraInfo>>,
Expand Down Expand Up @@ -75,6 +95,7 @@ impl ApiServerInMemoryStorage {
address_balance_table: BTreeMap::new(),
address_locked_balance_table: BTreeMap::new(),
address_transactions_table: BTreeMap::new(),
token_transactions_table: BTreeMap::new(),
delegation_table: BTreeMap::new(),
main_chain_blocks_table: BTreeMap::new(),
pool_data_table: BTreeMap::new(),
Expand Down Expand Up @@ -173,6 +194,27 @@ impl ApiServerInMemoryStorage {
}))
}

fn get_token_transactions(
&self,
token_id: TokenId,
len: u32,
tx_global_index: u64,
) -> Result<Vec<TokenTransaction>, ApiServerStorageError> {
Ok(self
.token_transactions_table
.get(&token_id)
.map_or_else(Vec::new, |transactions| {
transactions
.iter()
.rev()
.flat_map(|(_, txs)| txs.iter().map(|tx| &tx.0))
.flat_map(|tx| (tx.tx_global_index < tx_global_index).then_some(tx))
.cloned()
.take(len as usize)
.collect()
}))
Comment on lines +203 to +215
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look correct:

  1. The transactions in the inner set are not ordered by the global index. So, if there are more than len transactions at a particular height, the function will return arbitrary len transactions, not the latest len ones.
  2. Even if the inner set was ordered by the global index first, the initial .iter().rev().flat_map(...) would still mess it up, because it produces an iterator that goes backwards over the heights, but forward over transactions. So, the transactions wouldn't be ordered by the global index anyway and taking the first len of them is wrong.

P.S. It'd be nice to have a test for this in the storage test suite.

}

fn get_block(&self, block_id: Id<Block>) -> Result<Option<BlockInfo>, ApiServerStorageError> {
let block_result = self.block_table.get(&block_id);
let block = match block_result {
Expand Down Expand Up @@ -214,7 +256,7 @@ impl ApiServerInMemoryStorage {
additional_info: additinal_data.clone(),
},
block_aux: *block_aux,
global_tx_index: *tx_global_index,
tx_global_index: *tx_global_index,
}
},
)
Expand All @@ -240,7 +282,7 @@ impl ApiServerInMemoryStorage {
TransactionWithBlockInfo {
tx_info: tx_info.clone(),
block_aux: *block_aux,
global_tx_index: *tx_global_index,
tx_global_index: *tx_global_index,
}
})
.collect())
Expand Down Expand Up @@ -864,6 +906,20 @@ impl ApiServerInMemoryStorage {
Ok(())
}

fn del_token_transactions_above_height(
&mut self,
block_height: BlockHeight,
) -> Result<(), ApiServerStorageError> {
// Inefficient, but acceptable for testing with InMemoryStorage

self.token_transactions_table.retain(|_, v| {
v.retain(|k, _| k <= &block_height);
!v.is_empty()
});

Ok(())
}

fn set_address_balance_at_height(
&mut self,
address: &Address<Destination>,
Expand Down Expand Up @@ -942,6 +998,26 @@ impl ApiServerInMemoryStorage {
Ok(())
}

fn set_token_transaction_at_height(
&mut self,
token_id: TokenId,
tx_id: Id<Transaction>,
block_height: BlockHeight,
tx_global_index: u64,
) -> Result<(), ApiServerStorageError> {
self.token_transactions_table
.entry(token_id)
.or_default()
.entry(block_height)
.or_default()
.replace(TokenTransactionOrderedByTxId(TokenTransaction {
tx_global_index,
tx_id,
}));

Ok(())
}

fn set_mainchain_block(
&mut self,
block_id: Id<Block>,
Expand Down Expand Up @@ -1087,26 +1163,36 @@ impl ApiServerInMemoryStorage {
&mut self,
outpoint: UtxoOutPoint,
utxo: Utxo,
address: &str,
addresses: &[&str],
block_height: BlockHeight,
) -> Result<(), ApiServerStorageError> {
self.utxo_table.entry(outpoint.clone()).or_default().insert(block_height, utxo);
self.address_utxos.entry(address.into()).or_default().insert(outpoint);
for address in addresses {
self.address_utxos
.entry((*address).into())
.or_default()
.insert(outpoint.clone());
}
Ok(())
}

fn set_locked_utxo_at_height(
&mut self,
outpoint: UtxoOutPoint,
utxo: LockedUtxo,
address: &str,
addresses: &[&str],
block_height: BlockHeight,
) -> Result<(), ApiServerStorageError> {
self.locked_utxo_table
.entry(outpoint.clone())
.or_default()
.insert(block_height, utxo);
self.address_locked_utxos.entry(address.into()).or_default().insert(outpoint);
for address in addresses {
self.address_locked_utxos
.entry((*address).into())
.or_default()
.insert(outpoint.clone());
}
Ok(())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use common::{
use crate::storage::storage_api::{
block_aux_data::BlockAuxData, AmountWithDecimals, ApiServerStorageError, ApiServerStorageRead,
BlockInfo, CoinOrTokenStatistic, Delegation, FungibleTokenData, NftWithOwner, Order,
PoolBlockStats, PoolDataWithExtraInfo, TransactionInfo, TransactionWithBlockInfo, Utxo,
UtxoWithExtraInfo,
PoolBlockStats, PoolDataWithExtraInfo, TokenTransaction, TransactionInfo,
TransactionWithBlockInfo, Utxo, UtxoWithExtraInfo,
};

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

async fn get_token_transactions(
&self,
token_id: TokenId,
len: u32,
tx_global_index: u64,
) -> Result<Vec<TokenTransaction>, ApiServerStorageError> {
self.transaction.get_token_transactions(token_id, len, tx_global_index)
}

async fn get_block(
&self,
block_id: Id<Block>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use crate::storage::storage_api::{
block_aux_data::{BlockAuxData, BlockWithExtraData},
AmountWithDecimals, ApiServerStorageError, ApiServerStorageRead, ApiServerStorageWrite,
BlockInfo, CoinOrTokenStatistic, Delegation, FungibleTokenData, LockedUtxo, NftWithOwner,
Order, PoolBlockStats, PoolDataWithExtraInfo, TransactionInfo, TransactionWithBlockInfo, Utxo,
UtxoWithExtraInfo,
Order, PoolBlockStats, PoolDataWithExtraInfo, TokenTransaction, TransactionInfo,
TransactionWithBlockInfo, Utxo, UtxoWithExtraInfo,
};
use common::{
address::Address,
Expand Down Expand Up @@ -64,6 +64,13 @@ impl ApiServerStorageWrite for ApiServerInMemoryStorageTransactionalRw<'_> {
self.transaction.del_address_transactions_above_height(block_height)
}

async fn del_token_transactions_above_height(
&mut self,
block_height: BlockHeight,
) -> Result<(), ApiServerStorageError> {
self.transaction.del_token_transactions_above_height(block_height)
}

async fn set_address_balance_at_height(
&mut self,
address: &Address<Destination>,
Expand Down Expand Up @@ -104,6 +111,21 @@ impl ApiServerStorageWrite for ApiServerInMemoryStorageTransactionalRw<'_> {
.set_address_transactions_at_height(address, transactions, block_height)
}

async fn set_token_transaction_at_height(
&mut self,
token_id: TokenId,
tx_id: Id<Transaction>,
block_height: BlockHeight,
tx_global_index: u64,
) -> Result<(), ApiServerStorageError> {
self.transaction.set_token_transaction_at_height(
token_id,
tx_id,
block_height,
tx_global_index,
)
}

async fn set_mainchain_block(
&mut self,
block_id: Id<Block>,
Expand Down Expand Up @@ -176,21 +198,21 @@ impl ApiServerStorageWrite for ApiServerInMemoryStorageTransactionalRw<'_> {
&mut self,
outpoint: UtxoOutPoint,
utxo: Utxo,
address: &str,
addresses: &[&str],
block_height: BlockHeight,
) -> Result<(), ApiServerStorageError> {
self.transaction.set_utxo_at_height(outpoint, utxo, address, block_height)
self.transaction.set_utxo_at_height(outpoint, utxo, addresses, block_height)
}

async fn set_locked_utxo_at_height(
&mut self,
outpoint: UtxoOutPoint,
utxo: LockedUtxo,
address: &str,
addresses: &[&str],
block_height: BlockHeight,
) -> Result<(), ApiServerStorageError> {
self.transaction
.set_locked_utxo_at_height(outpoint, utxo, address, block_height)
.set_locked_utxo_at_height(outpoint, utxo, addresses, block_height)
}

async fn del_utxo_above_height(
Expand Down Expand Up @@ -331,6 +353,15 @@ impl ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRw<'_> {
self.transaction.get_address_transactions(address)
}

async fn get_token_transactions(
&self,
token_id: TokenId,
len: u32,
tx_global_index: u64,
) -> Result<Vec<TokenTransaction>, ApiServerStorageError> {
self.transaction.get_token_transactions(token_id, len, tx_global_index)
}

async fn get_latest_blocktimestamps(
&self,
) -> Result<Vec<BlockTimestamp>, ApiServerStorageError> {
Expand Down
2 changes: 1 addition & 1 deletion api-server/api-server-common/src/storage/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

pub const CURRENT_STORAGE_VERSION: u32 = 23;
pub const CURRENT_STORAGE_VERSION: u32 = 24;

pub mod in_memory;
pub mod postgres;
Loading