Skip to content

Commit

Permalink
Split asset name and policy id for simplified future searches
Browse files Browse the repository at this point in the history
  • Loading branch information
gostkin committed Dec 18, 2023
1 parent 03ef4ca commit 1e22d98
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 32 deletions.
3 changes: 2 additions & 1 deletion indexer/entity/src/projected_nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ pub struct Model {
pub hololocker_utxo_id: Option<i64>,
#[sea_orm(column_type = "BigInteger")]
pub tx_id: i64,
pub asset: String,
pub policy_id: String,
pub asset_name: String,
#[sea_orm(column_type = "BigInteger")]
pub amount: i64,
pub operation: i32, // lock / unlock / claim
Expand Down
3 changes: 2 additions & 1 deletion indexer/migration/src/m20231025_000017_projected_nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ impl MigrationTrait for Migration {
.col(ColumnDef::new(Column::PreviousUtxoTxOutputIndex).big_integer())
.col(ColumnDef::new(Column::HololockerUtxoId).big_integer())
.col(ColumnDef::new(Column::TxId).big_integer().not_null())
.col(ColumnDef::new(Column::Asset).text().not_null())
.col(ColumnDef::new(Column::AssetName).text().not_null())
.col(ColumnDef::new(Column::PolicyId).text().not_null())
.col(ColumnDef::new(Column::Amount).big_integer().not_null())
.col(ColumnDef::new(Column::Operation).integer().not_null())
.col(ColumnDef::new(Column::PlutusDatum).binary().not_null())
Expand Down
108 changes: 83 additions & 25 deletions indexer/tasks/src/multiera/multiera_projected_nft.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use anyhow::anyhow;
use cardano_multiplatform_lib::error::DeserializeError;
use cml_core::serialization::FromBytes;
use cml_crypto::RawBytesEncoding;
Expand Down Expand Up @@ -102,11 +101,18 @@ pub(crate) struct ProjectedNftInputsQueryOutputResult {
pub tx_hash: Vec<u8>,
pub operation: i32,
pub owner_address: Vec<u8>,
pub asset: String,
pub policy_id: String,
pub asset_name: String,
pub amount: i64,
pub plutus_datum: Vec<u8>,
}

impl ProjectedNftInputsQueryOutputResult {
pub fn subject(&self) -> String {
format!("{}.{}", self.policy_id, self.asset_name)
}
}

async fn handle_projected_nft(
db_tx: &DatabaseTransaction,
block: BlockInfo<'_, MultiEraBlock<'_>, BlockGlobalInfo>,
Expand Down Expand Up @@ -196,15 +202,16 @@ async fn handle_projected_nft(
}

for output_data in projected_nft_outputs.into_iter() {
for (asset_name, asset_value) in output_data.non_ada_assets.into_iter() {
for asset in output_data.non_ada_assets.into_iter() {
queued_projected_nft_records.push(entity::projected_nft::ActiveModel {
owner_address: Set(output_data.address.clone()),
previous_utxo_tx_output_index: Set(output_data.previous_utxo_tx_output_index),
previous_utxo_tx_hash: Set(output_data.previous_utxo_tx_hash.clone()),
hololocker_utxo_id: Set(Some(output_data.hololocker_utxo_id)),
tx_id: Set(cardano_transaction.id),
asset: Set(asset_name),
amount: Set(asset_value),
policy_id: Set(asset.policy_id),
asset_name: Set(asset.asset_name),
amount: Set(asset.amount),
operation: Set(output_data.operation.into()),
plutus_datum: Set(output_data.plutus_data.clone()),
for_how_long: Set(output_data.for_how_long),
Expand Down Expand Up @@ -236,7 +243,7 @@ fn find_lock_outputs_for_corresponding_partial_withdrawals(
}

let mut nft_data_assets = output_data.non_ada_assets.clone();
nft_data_assets.sort_by_key(|(name, _)| name.clone());
nft_data_assets.sort_by_key(|asset| asset.subject());

let mut withdrawal_input_to_remove: Option<(Vec<u8>, i64)> = None;

Expand All @@ -254,9 +261,13 @@ fn find_lock_outputs_for_corresponding_partial_withdrawals(

let mut withdrawal_assets = withdrawal
.iter()
.map(|w| (w.asset.clone(), w.amount))
.map(|w| AssetData {
policy_id: w.policy_id.clone(),
asset_name: w.asset_name.clone(),
amount: w.amount,
})
.collect::<Vec<_>>();
withdrawal_assets.sort_by_key(|(name, _)| name.clone());
withdrawal_assets.sort_by_key(|asset| asset.subject());

if withdrawal_assets == nft_data_assets {
withdrawal_input_to_remove = Some((input_hash.clone(), *input_index));
Expand Down Expand Up @@ -316,18 +327,19 @@ fn handle_partial_withdraw(
// make a balance map
let mut input_asset_to_value = HashMap::<String, ProjectedNftInputsQueryOutputResult>::new();
for entry in partial_withdrawal_input.iter() {
input_asset_to_value.insert(entry.asset.clone(), entry.clone());
input_asset_to_value.insert(entry.subject(), entry.clone());
}

// subtract all the assets
for (output_asset_name, output_asset_value) in output_projected_nft_data.non_ada_assets.iter() {
for output_asset_data in output_projected_nft_data.non_ada_assets.iter() {
let output_asset_subject = output_asset_data.subject();
input_asset_to_value
.get_mut(&output_asset_name.clone())
.get_mut(&output_asset_subject)
.ok_or(DbErr::Custom(format!(
"Expected to see asset {output_asset_name} in projected nft {}@{withdrawn_from_input_index}",
"Expected to see asset {output_asset_subject} in projected nft {}@{withdrawn_from_input_index}",
hex::encode(withdrawn_from_input_hash.clone())
)))?
.amount -= output_asset_value;
.amount -= output_asset_data.amount;
}

*partial_withdrawal_input = input_asset_to_value
Expand Down Expand Up @@ -384,7 +396,8 @@ async fn get_projected_nft_inputs(
.column(TransactionOutputColumn::TxId)
.column(TransactionOutputColumn::OutputIndex)
.column(ProjectedNftColumn::Operation)
.column(ProjectedNftColumn::Asset)
.column(ProjectedNftColumn::PolicyId)
.column(ProjectedNftColumn::AssetName)
.column(ProjectedNftColumn::Amount)
.column(ProjectedNftColumn::OwnerAddress)
.column(ProjectedNftColumn::PlutusDatum)
Expand Down Expand Up @@ -451,7 +464,8 @@ fn handle_claims_and_partial_withdraws(
queued_projected_nft_records.push(entity::projected_nft::ActiveModel {
hololocker_utxo_id: Set(None),
tx_id: Set(cardano_transaction.id),
asset: Set(projected_nft.asset.clone()),
policy_id: Set(projected_nft.policy_id.clone()),
asset_name: Set(projected_nft.asset_name.clone()),
amount: Set(projected_nft.amount),
operation: Set(ProjectedNftOperation::Claim.into()),
plutus_datum: Set(vec![]),
Expand Down Expand Up @@ -508,6 +522,47 @@ fn get_output_index_to_outputs_map(
outputs_map
}

#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub struct AssetData {
pub policy_id: String,
pub asset_name: String,
pub amount: i64,
}

impl AssetData {
pub fn subject(&self) -> String {
format!("{}.{}", self.policy_id, self.asset_name)
}

pub fn from_subject(subject: String, amount: i64) -> Result<AssetData, DbErr> {
let mut split = subject.split('.');
let policy_id = if let Some(policy_id_hex) = split.next() {
policy_id_hex.to_string()
} else {
return Err(DbErr::Custom(
"No policy id found in asset subject".to_string(),
));
};
let asset_name = if let Some(asset_name) = split.next() {
asset_name.to_string()
} else {
return Err(DbErr::Custom(
"No asset name found in asset subject".to_string(),
));
};
if let Some(next) = split.next() {
return Err(DbErr::Custom(format!(
"Extra information is found in asset: {next}"
)));
}
Ok(AssetData {
policy_id,
asset_name,
amount,
})
}
}

#[derive(Debug, Clone, Default)]
struct ProjectedNftData {
pub previous_utxo_tx_hash: Vec<u8>,
Expand All @@ -518,7 +573,7 @@ struct ProjectedNftData {
pub for_how_long: Option<i64>,
// this field is set only on unlocking outputs that were created through partial withdraw
pub partial_withdrawn_from_input: Option<(Vec<u8>, i64)>,
pub non_ada_assets: Vec<(String, i64)>,
pub non_ada_assets: Vec<AssetData>,
pub hololocker_utxo_id: i64,
}

Expand Down Expand Up @@ -583,16 +638,19 @@ fn extract_operation_and_datum(
let non_ada_assets = output
.non_ada_assets()
.iter()
.map(|asset| {
(
asset.subject(),
match asset {
Asset::Ada(value) => *value as i64,
Asset::NativeAsset(_, _, value) => *value as i64,
},
)
.map(|asset| match asset {
Asset::Ada(value) => AssetData {
policy_id: "".to_string(),
asset_name: "".to_string(),
amount: *value as i64,
},
Asset::NativeAsset(policy_id, asset_name, value) => AssetData {
policy_id: hex::encode(policy_id),
asset_name: hex::encode(asset_name.clone()),
amount: *value as i64,
},
})
.collect::<Vec<(String, i64)>>();
.collect::<Vec<AssetData>>();
match parsed.status {
Status::Locked => ProjectedNftData {
address: owner_address,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ export class ProjectedNftRangeController extends Controller {
previousTxOutputIndex: data.previous_tx_output_index != null ? parseInt(data.previous_tx_output_index) : null,
actionTxId: data.action_tx_id,
actionOutputIndex: data.action_output_index,
asset: data.asset,
asset: `${data.policy_id}.${data.asset_name}`,
policyId: data.policy_id,
assetName: data.asset_name,
amount: data.amount,
status: data.status,
plutusDatum: data.plutus_datum,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ export interface ISqlProjectedNftRangeResult {
action_slot: number;
action_tx_id: string | null;
amount: string;
asset: string;
asset_name: string;
for_how_long: string | null;
owner_address: string | null;
plutus_datum: string | null;
policy_id: string;
previous_tx_hash: string | null;
previous_tx_output_index: string | null;
status: string | null;
Expand All @@ -28,7 +29,7 @@ export interface ISqlProjectedNftRangeQuery {
result: ISqlProjectedNftRangeResult;
}

const sqlProjectedNftRangeIR: any = {"usedParamSet":{"min_slot":true,"max_slot":true},"params":[{"name":"min_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":1166,"b":1175}]},{"name":"max_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":1201,"b":1210}]}],"statement":"SELECT\n encode(\"ProjectedNFT\".owner_address, 'hex') as owner_address,\n\n encode(\"ProjectedNFT\".previous_utxo_tx_hash, 'hex') as previous_tx_hash,\n \"ProjectedNFT\".previous_utxo_tx_output_index as previous_tx_output_index,\n\n CASE\n WHEN \"TransactionOutput\".output_index = NULL THEN NULL\n ELSE \"TransactionOutput\".output_index\n END AS action_output_index,\n\n encode(\"Transaction\".hash, 'hex') as action_tx_id,\n\n \"ProjectedNFT\".asset as asset,\n \"ProjectedNFT\".amount as amount,\n\n CASE\n WHEN \"ProjectedNFT\".operation = 0 THEN 'Lock'\n WHEN \"ProjectedNFT\".operation = 1 THEN 'Unlocking'\n WHEN \"ProjectedNFT\".operation = 2 THEN 'Claim'\n ELSE 'Invalid'\n END AS status,\n\n encode(\"ProjectedNFT\".plutus_datum, 'hex') as plutus_datum,\n \"ProjectedNFT\".for_how_long as for_how_long,\n\n \"Block\".slot as action_slot\nFROM \"ProjectedNFT\"\n LEFT JOIN \"TransactionOutput\" ON \"TransactionOutput\".id = \"ProjectedNFT\".hololocker_utxo_id\n JOIN \"Transaction\" ON \"Transaction\".id = \"ProjectedNFT\".tx_id\n JOIN \"Block\" ON \"Transaction\".block_id = \"Block\".id\nWHERE\n \"Block\".slot > :min_slot!\n AND \"Block\".slot <= :max_slot!\nORDER BY (\"Block\".height, \"Transaction\".tx_index) ASC"};
const sqlProjectedNftRangeIR: any = {"usedParamSet":{"min_slot":true,"max_slot":true},"params":[{"name":"min_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":1219,"b":1228}]},{"name":"max_slot","required":true,"transform":{"type":"scalar"},"locs":[{"a":1254,"b":1263}]}],"statement":"SELECT\n encode(\"ProjectedNFT\".owner_address, 'hex') as owner_address,\n\n encode(\"ProjectedNFT\".previous_utxo_tx_hash, 'hex') as previous_tx_hash,\n \"ProjectedNFT\".previous_utxo_tx_output_index as previous_tx_output_index,\n\n CASE\n WHEN \"TransactionOutput\".output_index = NULL THEN NULL\n ELSE \"TransactionOutput\".output_index\n END AS action_output_index,\n\n encode(\"Transaction\".hash, 'hex') as action_tx_id,\n\n \"ProjectedNFT\".policy_id as policy_id,\n \"ProjectedNFT\".asset_name as asset_name,\n \"ProjectedNFT\".amount as amount,\n\n CASE\n WHEN \"ProjectedNFT\".operation = 0 THEN 'Lock'\n WHEN \"ProjectedNFT\".operation = 1 THEN 'Unlocking'\n WHEN \"ProjectedNFT\".operation = 2 THEN 'Claim'\n ELSE 'Invalid'\n END AS status,\n\n encode(\"ProjectedNFT\".plutus_datum, 'hex') as plutus_datum,\n \"ProjectedNFT\".for_how_long as for_how_long,\n\n \"Block\".slot as action_slot\nFROM \"ProjectedNFT\"\n LEFT JOIN \"TransactionOutput\" ON \"TransactionOutput\".id = \"ProjectedNFT\".hololocker_utxo_id\n JOIN \"Transaction\" ON \"Transaction\".id = \"ProjectedNFT\".tx_id\n JOIN \"Block\" ON \"Transaction\".block_id = \"Block\".id\nWHERE\n \"Block\".slot > :min_slot!\n AND \"Block\".slot <= :max_slot!\nORDER BY (\"Block\".height, \"Transaction\".tx_index) ASC"};

/**
* Query generated from SQL:
Expand All @@ -46,7 +47,8 @@ const sqlProjectedNftRangeIR: any = {"usedParamSet":{"min_slot":true,"max_slot":
*
* encode("Transaction".hash, 'hex') as action_tx_id,
*
* "ProjectedNFT".asset as asset,
* "ProjectedNFT".policy_id as policy_id,
* "ProjectedNFT".asset_name as asset_name,
* "ProjectedNFT".amount as amount,
*
* CASE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ SELECT

encode("Transaction".hash, 'hex') as action_tx_id,

"ProjectedNFT".asset as asset,
"ProjectedNFT".policy_id as policy_id,
"ProjectedNFT".asset_name as asset_name,
"ProjectedNFT".amount as amount,

CASE
Expand Down
14 changes: 14 additions & 0 deletions webserver/shared/models/ProjectedNftRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ export type ProjectedNftRangeResponse = {
* @example "96f7dc9749ede0140f042516f4b723d7261610d6b12ccb19f3475278.415045"
*/
asset: string,
/**
* Asset policy id that relates to Projected NFT event
*
* @pattern [0-9a-fA-F]+.[0-9a-fA-F]+
* @example "96f7dc9749ede0140f042516f4b723d7261610d6b12ccb19f3475278"
*/
policyId: string,
/**
* Asset name that relates to Projected NFT event
*
* @pattern [0-9a-fA-F]+.[0-9a-fA-F]+
* @example "415045"
*/
assetName: string,
/**
* Number of assets of `asset` type used in this Projected NFT event.
*
Expand Down

0 comments on commit 1e22d98

Please sign in to comment.