Skip to content

Commit

Permalink
Add NFT owner to API Server response
Browse files Browse the repository at this point in the history
  • Loading branch information
OBorce committed Dec 24, 2024
1 parent b64ed81 commit 8619f18
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 73 deletions.
52 changes: 42 additions & 10 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 @@ -18,9 +18,11 @@ pub mod transactional;
use crate::storage::storage_api::{
block_aux_data::{BlockAuxData, BlockWithExtraData},
ApiServerStorageError, BlockInfo, CoinOrTokenStatistic, Delegation, FungibleTokenData,
LockedUtxo, Order, PoolBlockStats, TransactionInfo, Utxo, UtxoLock, UtxoWithExtraInfo,
LockedUtxo, NftWithOwner, Order, PoolBlockStats, TransactionInfo, Utxo, UtxoLock,
UtxoWithExtraInfo,
};
use common::{
address::Address,
chain::{
block::timestamp::BlockTimestamp,
tokens::{NftIssuance, TokenId},
Expand Down Expand Up @@ -55,7 +57,7 @@ struct ApiServerInMemoryStorage {
locked_utxo_table: BTreeMap<UtxoOutPoint, BTreeMap<BlockHeight, LockedUtxo>>,
address_locked_utxos: BTreeMap<String, BTreeSet<UtxoOutPoint>>,
fungible_token_issuances: BTreeMap<TokenId, BTreeMap<BlockHeight, FungibleTokenData>>,
nft_token_issuances: BTreeMap<TokenId, BTreeMap<BlockHeight, NftIssuance>>,
nft_token_issuances: BTreeMap<TokenId, BTreeMap<BlockHeight, NftWithOwner>>,
statistics:
BTreeMap<CoinOrTokenStatistic, BTreeMap<CoinOrTokenId, BTreeMap<BlockHeight, Amount>>>,
orders_table: BTreeMap<OrderId, BTreeMap<BlockHeight, Order>>,
Expand Down Expand Up @@ -599,7 +601,7 @@ impl ApiServerInMemoryStorage {
fn get_nft_token_issuance(
&self,
token_id: TokenId,
) -> Result<Option<NftIssuance>, ApiServerStorageError> {
) -> Result<Option<NftWithOwner>, ApiServerStorageError> {
Ok(self
.nft_token_issuances
.get(&token_id)
Expand Down Expand Up @@ -641,7 +643,7 @@ impl ApiServerInMemoryStorage {
(value.values().last().expect("not empty").token_ticker == ticker).then_some(key)
})
.chain(self.nft_token_issuances.iter().filter_map(|(key, value)| {
let value_ticker = match &value.values().last().expect("not empty") {
let value_ticker = match &value.values().last().expect("not empty").nft {
NftIssuance::V0(data) => data.metadata.ticker(),
};
(value_ticker == ticker).then_some(key)
Expand Down Expand Up @@ -803,7 +805,7 @@ impl ApiServerInMemoryStorage {

fn set_address_balance_at_height(
&mut self,
address: &str,
address: &Address<Destination>,
amount: Amount,
coin_or_token_id: CoinOrTokenId,
block_height: BlockHeight,
Expand All @@ -815,12 +817,36 @@ impl ApiServerInMemoryStorage {
.and_modify(|e| *e = amount)
.or_insert(amount);

self.update_nft_owner(coin_or_token_id, amount, address, block_height);

Ok(())
}

fn update_nft_owner(
&mut self,
coin_or_token_id: CoinOrTokenId,
amount: Amount,
address: &Address<Destination>,
block_height: BlockHeight,
) {
let CoinOrTokenId::TokenId(token_id) = coin_or_token_id else {
return;
};

if let Some(by_height) = self.nft_token_issuances.get_mut(&token_id) {
let last = by_height.values().last().expect("not empty");
let owner = (amount > Amount::ZERO).then_some(address.as_object().clone());
let new = NftWithOwner {
nft: last.nft.clone(),
owner,
};
by_height.insert(block_height, new);
};
}

fn set_address_locked_balance_at_height(
&mut self,
address: &str,
address: &Address<Destination>,
amount: Amount,
coin_or_token_id: CoinOrTokenId,
block_height: BlockHeight,
Expand All @@ -832,6 +858,8 @@ impl ApiServerInMemoryStorage {
.and_modify(|e| *e = amount)
.or_insert(amount);

self.update_nft_owner(coin_or_token_id, amount, address, block_height);

Ok(())
}

Expand Down Expand Up @@ -1060,11 +1088,15 @@ impl ApiServerInMemoryStorage {
token_id: TokenId,
block_height: BlockHeight,
issuance: NftIssuance,
owner: &Destination,
) -> Result<(), ApiServerStorageError> {
self.nft_token_issuances
.entry(token_id)
.or_default()
.insert(block_height, issuance);
self.nft_token_issuances.entry(token_id).or_default().insert(
block_height,
NftWithOwner {
nft: issuance,
owner: Some(owner.clone()),
},
);
Ok(())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,17 @@ use std::collections::BTreeMap;

use common::{
chain::{
block::timestamp::BlockTimestamp,
tokens::{NftIssuance, TokenId},
Block, DelegationId, Destination, OrderId, PoolId, Transaction, UtxoOutPoint,
block::timestamp::BlockTimestamp, tokens::TokenId, Block, DelegationId, Destination,
OrderId, PoolId, Transaction, UtxoOutPoint,
},
primitives::{Amount, BlockHeight, CoinOrTokenId, Id},
};
use pos_accounting::PoolData;

use crate::storage::storage_api::{
block_aux_data::BlockAuxData, ApiServerStorageError, ApiServerStorageRead, BlockInfo,
CoinOrTokenStatistic, Delegation, FungibleTokenData, Order, PoolBlockStats, TransactionInfo,
Utxo, UtxoWithExtraInfo,
CoinOrTokenStatistic, Delegation, FungibleTokenData, NftWithOwner, Order, PoolBlockStats,
TransactionInfo, Utxo, UtxoWithExtraInfo,
};

use super::ApiServerInMemoryStorageTransactionalRo;
Expand Down Expand Up @@ -217,7 +216,7 @@ impl ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRo<'_> {
async fn get_nft_token_issuance(
&self,
token_id: TokenId,
) -> Result<Option<NftIssuance>, ApiServerStorageError> {
) -> Result<Option<NftWithOwner>, ApiServerStorageError> {
self.transaction.get_nft_token_issuance(token_id)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use std::collections::{BTreeMap, BTreeSet};

use common::{
address::Address,
chain::{
block::timestamp::BlockTimestamp,
tokens::{NftIssuance, TokenId},
Expand All @@ -28,8 +29,8 @@ use pos_accounting::PoolData;
use crate::storage::storage_api::{
block_aux_data::{BlockAuxData, BlockWithExtraData},
ApiServerStorageError, ApiServerStorageRead, ApiServerStorageWrite, BlockInfo,
CoinOrTokenStatistic, Delegation, FungibleTokenData, LockedUtxo, Order, PoolBlockStats,
TransactionInfo, Utxo, UtxoWithExtraInfo,
CoinOrTokenStatistic, Delegation, FungibleTokenData, LockedUtxo, NftWithOwner, Order,
PoolBlockStats, TransactionInfo, Utxo, UtxoWithExtraInfo,
};

use super::ApiServerInMemoryStorageTransactionalRw;
Expand Down Expand Up @@ -66,7 +67,7 @@ impl ApiServerStorageWrite for ApiServerInMemoryStorageTransactionalRw<'_> {

async fn set_address_balance_at_height(
&mut self,
address: &str,
address: &Address<Destination>,
amount: Amount,
coin_or_token_id: CoinOrTokenId,
block_height: BlockHeight,
Expand All @@ -81,7 +82,7 @@ impl ApiServerStorageWrite for ApiServerInMemoryStorageTransactionalRw<'_> {

async fn set_address_locked_balance_at_height(
&mut self,
address: &str,
address: &Address<Destination>,
amount: Amount,
coin_or_token_id: CoinOrTokenId,
block_height: BlockHeight,
Expand Down Expand Up @@ -219,8 +220,9 @@ impl ApiServerStorageWrite for ApiServerInMemoryStorageTransactionalRw<'_> {
token_id: TokenId,
block_height: BlockHeight,
issuance: NftIssuance,
owner: &Destination,
) -> Result<(), ApiServerStorageError> {
self.transaction.set_nft_token_issuance(token_id, block_height, issuance)
self.transaction.set_nft_token_issuance(token_id, block_height, issuance, owner)
}

async fn del_token_issuance_above_height(
Expand Down Expand Up @@ -456,7 +458,7 @@ impl ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRw<'_> {
async fn get_nft_token_issuance(
&self,
token_id: TokenId,
) -> Result<Option<NftIssuance>, ApiServerStorageError> {
) -> Result<Option<NftWithOwner>, ApiServerStorageError> {
self.transaction.get_nft_token_issuance(token_id)
}

Expand Down
90 changes: 80 additions & 10 deletions api-server/api-server-common/src/storage/impls/postgres/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ use crate::storage::{
storage_api::{
block_aux_data::{BlockAuxData, BlockWithExtraData},
ApiServerStorageError, BlockInfo, CoinOrTokenStatistic, Delegation, FungibleTokenData,
LockedUtxo, Order, PoolBlockStats, TransactionInfo, Utxo, UtxoWithExtraInfo,
LockedUtxo, NftWithOwner, Order, PoolBlockStats, TransactionInfo, Utxo, UtxoWithExtraInfo,
},
};

Expand Down Expand Up @@ -240,7 +240,7 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {

pub async fn set_address_balance_at_height(
&mut self,
address: &str,
address: &Address<Destination>,
amount: Amount,
coin_or_token_id: CoinOrTokenId,
block_height: BlockHeight,
Expand All @@ -260,12 +260,62 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
.await
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?;

let CoinOrTokenId::TokenId(token_id) = coin_or_token_id else {
return Ok(());
};

self.update_nft_owner(token_id, height, (amount > Amount::ZERO).then_some(address))
.await?;

Ok(())
}

async fn update_nft_owner(
&mut self,
token_id: TokenId,
height: i64,
owner: Option<&Address<Destination>>,
) -> Result<(), ApiServerStorageError> {
self.tx
.execute(
r#"
WITH LastRow AS (
SELECT
nft_id,
block_height,
ticker,
issuance
FROM
ml.nft_issuance
WHERE
nft_id = $1
ORDER BY
block_height DESC
LIMIT 1
)
INSERT INTO ml.nft_issuance (nft_id, block_height, ticker, issuance, owner)
SELECT
lr.nft_id,
$2,
lr.ticker,
lr.issuance,
$3
FROM
LastRow lr
ON CONFLICT (nft_id, block_height) DO UPDATE
SET
ticker = EXCLUDED.ticker,
owner = EXCLUDED.owner;"#,
&[&token_id.encode(), &height, &owner.map(|o| o.as_object().encode())],
)
.await
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?;
Ok(())
}

pub async fn set_address_locked_balance_at_height(
&mut self,
address: &str,
address: &Address<Destination>,
amount: Amount,
coin_or_token_id: CoinOrTokenId,
block_height: BlockHeight,
Expand All @@ -285,6 +335,13 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
.await
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?;

let CoinOrTokenId::TokenId(token_id) = coin_or_token_id else {
return Ok(());
};

self.update_nft_owner(token_id, height, (amount > Amount::ZERO).then_some(address))
.await?;

Ok(())
}

Expand Down Expand Up @@ -631,7 +688,8 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
block_height bigint NOT NULL,
ticker bytea NOT NULL,
issuance bytea NOT NULL,
PRIMARY KEY (nft_id)
owner bytea,
PRIMARY KEY (nft_id, block_height)
);",
)
.await?;
Expand Down Expand Up @@ -2030,11 +2088,11 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
pub async fn get_nft_token_issuance(
&self,
token_id: TokenId,
) -> Result<Option<NftIssuance>, ApiServerStorageError> {
) -> Result<Option<NftWithOwner>, ApiServerStorageError> {
let row = self
.tx
.query_opt(
"SELECT issuance FROM ml.nft_issuance WHERE nft_id = $1
"SELECT issuance, owner FROM ml.nft_issuance WHERE nft_id = $1
ORDER BY block_height DESC
LIMIT 1;",
&[&token_id.encode()],
Expand All @@ -2048,22 +2106,34 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
};

let serialized_data: Vec<u8> = row.get(0);
let owner: Option<Vec<u8>> = row.get(1);

let issuance = NftIssuance::decode_all(&mut serialized_data.as_slice()).map_err(|e| {
let nft = NftIssuance::decode_all(&mut serialized_data.as_slice()).map_err(|e| {
ApiServerStorageError::DeserializationError(format!(
"Nft issuance data for nft id {} deserialization failed: {}",
token_id, e
))
})?;

Ok(Some(issuance))
let owner = owner
.map(|owner| {
Destination::decode_all(&mut owner.as_slice()).map_err(|e| {
ApiServerStorageError::DeserializationError(format!(
"Deserialization failed for nft owner {token_id}: {e}"
))
})
})
.transpose()?;

Ok(Some(NftWithOwner { nft, owner }))
}

pub async fn set_nft_token_issuance(
&mut self,
token_id: TokenId,
block_height: BlockHeight,
issuance: NftIssuance,
owner: &Destination,
) -> Result<(), ApiServerStorageError> {
let height = Self::block_height_to_postgres_friendly(block_height);

Expand All @@ -2073,8 +2143,8 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {

self.tx
.execute(
"INSERT INTO ml.nft_issuance (nft_id, block_height, issuance, ticker) VALUES ($1, $2, $3, $4);",
&[&token_id.encode(), &height, &issuance.encode(), ticker],
"INSERT INTO ml.nft_issuance (nft_id, block_height, issuance, ticker, owner) VALUES ($1, $2, $3, $4, $5);",
&[&token_id.encode(), &height, &issuance.encode(), ticker, &owner.encode()],
)
.await
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?;
Expand Down
Loading

0 comments on commit 8619f18

Please sign in to comment.