Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split asset name and policy id for simplified future searches #167

Merged
merged 9 commits into from
Dec 26, 2023
Merged
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
45 changes: 33 additions & 12 deletions docs/bin/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -757,15 +757,23 @@
],
"type": "object"
},
"ProjectedNftStatus": {
"enum": [
"Lock",
"Unlocking",
"Claim",
"Invalid"
],
"type": "string"
},
"ProjectedNftRangeResponse": {
"items": {
"properties": {
"forHowLong": {
"type": "number",
"format": "double",
"type": "string",
"nullable": true,
"description": "UNIX timestamp till which the funds can't be claimed in the Unlocking state.\nIf the status is not Unlocking this is always null.",
"example": 1701266986000
"example": "1701266986000"
},
"plutusDatum": {
"type": "string",
Expand All @@ -775,22 +783,31 @@
"pattern": "[0-9a-fA-F]+"
},
"status": {
"type": "string",
"allOf": [
{
"$ref": "#/components/schemas/ProjectedNftStatus"
}
],
"nullable": true,
"description": "Projected NFT status: Lock / Unlocking / Claim / Invalid",
"example": "Lock"
},
"amount": {
"type": "number",
"format": "double",
"type": "string",
"description": "Number of assets of `asset` type used in this Projected NFT event.",
"example": 1
"example": "1"
},
"asset": {
"assetName": {
"type": "string",
"description": "Asset that relates to Projected NFT event. Consists of 2 parts: PolicyId and AssetName",
"example": "96f7dc9749ede0140f042516f4b723d7261610d6b12ccb19f3475278.415045",
"pattern": "[0-9a-fA-F]+.[0-9a-fA-F]+"
"description": "Asset name that relates to Projected NFT event",
"example": "415045",
"pattern": "([0-9a-fA-F]{2}){0,32}"
},
"policyId": {
"type": "string",
"description": "Asset policy id that relates to Projected NFT event",
"example": "96f7dc9749ede0140f042516f4b723d7261610d6b12ccb19f3475278",
"pattern": "[0-9a-fA-F]{56}"
},
"previousTxOutputIndex": {
"type": "number",
Expand Down Expand Up @@ -839,7 +856,8 @@
"plutusDatum",
"status",
"amount",
"asset",
"assetName",
"policyId",
"previousTxOutputIndex",
"previousTxHash",
"actionOutputIndex",
Expand All @@ -853,6 +871,9 @@
},
"ProjectedNftRangeRequest": {
"properties": {
"address": {
"type": "string"
},
"range": {
"properties": {
"maxSlot": {
Expand Down
12 changes: 6 additions & 6 deletions docs/docs/indexer/Tasks/MultiEraProjectedNftTask.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ Parses projected NFT contract data
<summary>Configuration</summary>

```rust
use super::PayloadConfig::PayloadConfig;
use super::ReadonlyConfig::ReadonlyConfig;
use pallas::ledger::addresses::Address;
use pallas::ledger::primitives::alonzo::PlutusScript;
use pallas::ledger::primitives::babbage::PlutusV2Script;

#[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize)]
pub struct PayloadAndReadonlyConfig {
pub include_payload: bool,
pub readonly: bool,
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct AddressConfig {
pub address: String,
}

```
Expand Down
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
Loading
Loading