rpc changes for asset reporting
This commit is contained in:
parent
61bc832ff4
commit
eef98c4a00
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::common::binary_conversions::binary_to_string;
|
use crate::common::binary_conversions::binary_to_string;
|
||||||
|
use crate::encode;
|
||||||
use crate::rpc::responses::RpcResponse;
|
use crate::rpc::responses::RpcResponse;
|
||||||
use crate::sled::Db;
|
use crate::sled::Db;
|
||||||
|
|
||||||
|
|
@ -24,7 +25,14 @@ pub async fn get_tokens(db: &Db) -> RpcResponse {
|
||||||
.get(&key)
|
.get(&key)
|
||||||
.ok()
|
.ok()
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|bytes| bytes.to_vec())
|
.map(|bytes| {
|
||||||
|
let bytes = bytes.to_vec();
|
||||||
|
if bytes.len() == 32 {
|
||||||
|
encode(&bytes).into_bytes()
|
||||||
|
} else {
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if hash.len() != 64 {
|
if hash.len() != 64 {
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,11 @@ async fn find_origin_hash(
|
||||||
// Prefer the dedicated token-origins tree, but fall back to scanning
|
// Prefer the dedicated token-origins tree, but fall back to scanning
|
||||||
// saved txids if the origin mapping is missing.
|
// saved txids if the origin mapping is missing.
|
||||||
if let Ok(Some(bytes)) = origins_tree.get(matched_ticker.as_bytes()) {
|
if let Ok(Some(bytes)) = origins_tree.get(matched_ticker.as_bytes()) {
|
||||||
return Some(binary_to_string(bytes.to_vec()));
|
let origin_bytes = bytes.to_vec();
|
||||||
|
if origin_bytes.len() == 32 {
|
||||||
|
return Some(encode(&origin_bytes));
|
||||||
|
}
|
||||||
|
return Some(binary_to_string(origin_bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
let txid_tree = db.open_tree("txid").ok()?;
|
let txid_tree = db.open_tree("txid").ok()?;
|
||||||
|
|
@ -197,7 +201,6 @@ pub async fn lookup_token_details(db: &Db, token_name: String) -> RpcResponse {
|
||||||
if normalized_query.is_empty() {
|
if normalized_query.is_empty() {
|
||||||
return RpcResponse::Binary(b"error: Token name required".to_vec());
|
return RpcResponse::Binary(b"error: Token name required".to_vec());
|
||||||
}
|
}
|
||||||
|
|
||||||
let tokens_tree = db.open_tree("tokens").unwrap();
|
let tokens_tree = db.open_tree("tokens").unwrap();
|
||||||
let origins_tree = db.open_tree("token_origins").unwrap();
|
let origins_tree = db.open_tree("token_origins").unwrap();
|
||||||
let limits_tree = db.open_tree("token_limits").unwrap();
|
let limits_tree = db.open_tree("token_limits").unwrap();
|
||||||
|
|
@ -297,85 +300,100 @@ pub async fn lookup_token_details(db: &Db, token_name: String) -> RpcResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn token_catalog(db: &Db) -> RpcResponse {
|
pub async fn token_catalog(db: &Db) -> RpcResponse {
|
||||||
let tokens_tree = match db.open_tree("tokens") {
|
const TOKEN_LIST_ROW_BYTES: usize = 15 + 64;
|
||||||
Ok(tree) => tree,
|
const TOKEN_NAME_BYTES: usize = 15;
|
||||||
Err(err) => {
|
const TOKEN_HASH_BYTES: usize = 32;
|
||||||
return RpcResponse::Binary(
|
const TOKEN_COUNT_BYTES: usize = 8;
|
||||||
format!("error: Failed to open tokens tree: {err}").into_bytes(),
|
const TOKEN_SPREAD_BYTES: usize = 4;
|
||||||
)
|
const ISSUE_COUNT_BYTES: usize = 4;
|
||||||
}
|
const BURN_COUNT_BYTES: usize = 4;
|
||||||
};
|
|
||||||
let origins_tree = match db.open_tree("token_origins") {
|
|
||||||
Ok(tree) => tree,
|
|
||||||
Err(err) => {
|
|
||||||
return RpcResponse::Binary(
|
|
||||||
format!("error: Failed to open token origins tree: {err}").into_bytes(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let limits_tree = match db.open_tree("token_limits") {
|
|
||||||
Ok(tree) => tree,
|
|
||||||
Err(err) => {
|
|
||||||
return RpcResponse::Binary(
|
|
||||||
format!("error: Failed to open token limits tree: {err}").into_bytes(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut rows = Vec::new();
|
let mut rows = Vec::new();
|
||||||
|
let RpcResponse::Binary(token_list_bytes) = crate::rpc::commands::token_list::get_tokens(db).await;
|
||||||
|
|
||||||
for entry in origins_tree.iter() {
|
if token_list_bytes.len() % TOKEN_LIST_ROW_BYTES != 0 {
|
||||||
let (ticker_key, origin_value) = match entry {
|
return RpcResponse::Binary(b"error: Token list response had an unexpected byte length".to_vec());
|
||||||
Ok(pair) => pair,
|
}
|
||||||
|
|
||||||
|
for token_row in token_list_bytes.chunks_exact(TOKEN_LIST_ROW_BYTES) {
|
||||||
|
let ticker = binary_to_string(token_row[0..TOKEN_NAME_BYTES].to_vec());
|
||||||
|
if ticker.trim().is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let RpcResponse::Binary(details) = lookup_token_details(db, ticker.clone()).await;
|
||||||
|
if details.starts_with(b"error:") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fixed_details_len = TOKEN_NAME_BYTES
|
||||||
|
+ TOKEN_HASH_BYTES
|
||||||
|
+ Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
||||||
|
+ TOKEN_COUNT_BYTES
|
||||||
|
+ TOKEN_SPREAD_BYTES
|
||||||
|
+ 1
|
||||||
|
+ ISSUE_COUNT_BYTES;
|
||||||
|
if details.len() < fixed_details_len {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut offset = 0usize;
|
||||||
|
let ticker_bytes = details[offset..offset + TOKEN_NAME_BYTES].to_vec();
|
||||||
|
offset += TOKEN_NAME_BYTES;
|
||||||
|
|
||||||
|
let origin_hash = details[offset..offset + TOKEN_HASH_BYTES].to_vec();
|
||||||
|
offset += TOKEN_HASH_BYTES;
|
||||||
|
|
||||||
|
let creator = details[offset..offset + Wallet::SHORT_ADDRESS_BYTES_LENGTH].to_vec();
|
||||||
|
offset += Wallet::SHORT_ADDRESS_BYTES_LENGTH;
|
||||||
|
|
||||||
|
let current_supply = match details[offset..offset + TOKEN_COUNT_BYTES].try_into() {
|
||||||
|
Ok(bytes) => u64::from_le_bytes(bytes),
|
||||||
Err(_) => continue,
|
Err(_) => continue,
|
||||||
};
|
};
|
||||||
let ticker = binary_to_string(ticker_key.to_vec());
|
offset += TOKEN_COUNT_BYTES;
|
||||||
|
|
||||||
|
let holder_count = match details[offset..offset + TOKEN_SPREAD_BYTES].try_into() {
|
||||||
|
Ok(bytes) => u32::from_le_bytes(bytes),
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
offset += TOKEN_SPREAD_BYTES;
|
||||||
|
|
||||||
|
let hard_limit = details[offset];
|
||||||
|
offset += 1;
|
||||||
|
|
||||||
|
let issue_count = match details[offset..offset + ISSUE_COUNT_BYTES].try_into() {
|
||||||
|
Ok(bytes) => u32::from_le_bytes(bytes) as usize,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
offset += ISSUE_COUNT_BYTES;
|
||||||
|
|
||||||
|
let Some(issue_hash_bytes) = issue_count.checked_mul(TOKEN_HASH_BYTES) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if details.len().saturating_sub(offset) < issue_hash_bytes + BURN_COUNT_BYTES {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
offset += issue_hash_bytes;
|
||||||
|
|
||||||
|
let burn_count = match details[offset..offset + BURN_COUNT_BYTES].try_into() {
|
||||||
|
Ok(bytes) => u32::from_le_bytes(bytes) as usize,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
offset += BURN_COUNT_BYTES;
|
||||||
|
|
||||||
|
let Some(burn_hash_bytes) = burn_count.checked_mul(TOKEN_HASH_BYTES) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if details.len().saturating_sub(offset) != burn_hash_bytes {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let normalized_ticker = strip_spaces_and_lowercase(&ticker);
|
let normalized_ticker = strip_spaces_and_lowercase(&ticker);
|
||||||
if normalized_ticker.is_empty() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let origin_hash = binary_to_string(origin_value.to_vec());
|
|
||||||
let origin_hash_bytes = match decode(&origin_hash) {
|
|
||||||
Ok(bytes) if bytes.len() == 32 => bytes,
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
let RpcResponse::Binary(tx_bytes) =
|
|
||||||
request_transaction_by_txid(db, origin_hash_bytes.clone()).await;
|
|
||||||
if tx_bytes.is_empty() || tx_bytes[0] != 3 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let contract = match CreateTokenTransaction::from_bytes(tx_bytes[0], &tx_bytes[1..]).await {
|
|
||||||
Ok(tx) => tx,
|
|
||||||
Err(_) => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
let creator_bytes =
|
|
||||||
match Wallet::short_address_to_bytes(&contract.unsigned_create_token.creator) {
|
|
||||||
Some(bytes) => bytes,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
let current_supply = tokens_tree
|
|
||||||
.get(ticker_key.as_ref())
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.and_then(|value| parse_token_supply(value.as_ref()))
|
|
||||||
.unwrap_or(0);
|
|
||||||
let hard_limit = limits_tree
|
|
||||||
.get(ticker_key.as_ref())
|
|
||||||
.ok()
|
|
||||||
.flatten()
|
|
||||||
.and_then(|bytes| bytes.first().copied())
|
|
||||||
.unwrap_or(contract.unsigned_create_token.hard_limit);
|
|
||||||
|
|
||||||
let (_, burned_hashes) = collect_token_history(db, &ticker, &origin_hash_bytes).await;
|
|
||||||
let mut total_burned = 0_u64;
|
let mut total_burned = 0_u64;
|
||||||
for hash in burned_hashes {
|
for burn_hash in details[offset..].chunks_exact(TOKEN_HASH_BYTES) {
|
||||||
let RpcResponse::Binary(burn_bytes) =
|
let RpcResponse::Binary(burn_bytes) =
|
||||||
request_transaction_by_txid(db, hash.to_vec()).await;
|
request_transaction_by_txid(db, burn_hash.to_vec()).await;
|
||||||
if burn_bytes.is_empty() || burn_bytes[0] != 10 {
|
if burn_bytes.is_empty() || burn_bytes[0] != 10 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -392,26 +410,28 @@ pub async fn token_catalog(db: &Db) -> RpcResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut padded_ticker = ticker.as_bytes().to_vec();
|
let RpcResponse::Binary(origin_tx_bytes) =
|
||||||
if padded_ticker.len() < 15 {
|
request_transaction_by_txid(db, origin_hash.clone()).await;
|
||||||
padded_ticker.resize(15, b' ');
|
let contract = if !origin_tx_bytes.is_empty() && origin_tx_bytes[0] == 3 {
|
||||||
}
|
CreateTokenTransaction::from_bytes(origin_tx_bytes[0], &origin_tx_bytes[1..])
|
||||||
if padded_ticker.len() != 15
|
.await
|
||||||
|| creator_bytes.len() != Wallet::SHORT_ADDRESS_BYTES_LENGTH
|
.ok()
|
||||||
|| origin_hash_bytes.len() != 32
|
} else {
|
||||||
{
|
None
|
||||||
|
};
|
||||||
|
let Some(contract) = contract else {
|
||||||
continue;
|
continue;
|
||||||
}
|
};
|
||||||
|
|
||||||
rows.push((
|
rows.push((
|
||||||
padded_ticker,
|
ticker_bytes,
|
||||||
origin_hash_bytes,
|
origin_hash,
|
||||||
creator_bytes,
|
creator,
|
||||||
contract.unsigned_create_token.time,
|
contract.unsigned_create_token.time,
|
||||||
contract.unsigned_create_token.number,
|
contract.unsigned_create_token.number,
|
||||||
current_supply,
|
current_supply,
|
||||||
total_burned,
|
total_burned,
|
||||||
count_wallets_holding(&normalized_ticker) as u32,
|
holder_count,
|
||||||
hard_limit,
|
hard_limit,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue