mirror of
https://github.com/alexgo-io/bitcoin-indexer.git
synced 2026-01-12 16:52:57 +08:00
feat: attempt to support cursed inscriptions
This commit is contained in:
@@ -362,7 +362,7 @@ pub fn find_latest_inscription_number_at_block_height(
|
||||
block_height: &u64,
|
||||
inscriptions_db_conn: &Connection,
|
||||
_ctx: &Context,
|
||||
) -> Result<Option<u64>, String> {
|
||||
) -> Result<Option<i64>, String> {
|
||||
let args: &[&dyn ToSql] = &[&block_height.to_sql().unwrap()];
|
||||
let mut stmt = inscriptions_db_conn
|
||||
.prepare(
|
||||
@@ -373,25 +373,28 @@ pub fn find_latest_inscription_number_at_block_height(
|
||||
.query(args)
|
||||
.map_err(|e| format!("unable to query inscriptions: {}", e.to_string()))?;
|
||||
while let Ok(Some(row)) = rows.next() {
|
||||
let inscription_number: u64 = row.get(0).unwrap();
|
||||
let inscription_number: i64 = row.get(0).unwrap();
|
||||
return Ok(Some(inscription_number));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn find_latest_inscription_number(
|
||||
pub fn find_latest_cursed_inscription_number_at_block_height(
|
||||
block_height: &u64,
|
||||
inscriptions_db_conn: &Connection,
|
||||
_ctx: &Context,
|
||||
) -> Result<Option<u64>, String> {
|
||||
let args: &[&dyn ToSql] = &[];
|
||||
) -> Result<Option<i64>, String> {
|
||||
let args: &[&dyn ToSql] = &[&block_height.to_sql().unwrap()];
|
||||
let mut stmt = inscriptions_db_conn
|
||||
.prepare(
|
||||
"SELECT inscription_number FROM inscriptions ORDER BY inscription_number DESC LIMIT 1",
|
||||
"SELECT inscription_number FROM inscriptions WHERE block_height < ? ORDER BY inscription_number ASC LIMIT 1",
|
||||
)
|
||||
.unwrap();
|
||||
let mut rows = stmt.query(args).unwrap();
|
||||
.map_err(|e| format!("unable to query inscriptions: {}", e.to_string()))?;
|
||||
let mut rows = stmt
|
||||
.query(args)
|
||||
.map_err(|e| format!("unable to query inscriptions: {}", e.to_string()))?;
|
||||
while let Ok(Some(row)) = rows.next() {
|
||||
let inscription_number: u64 = row.get(0).unwrap();
|
||||
let inscription_number: i64 = row.get(0).unwrap();
|
||||
return Ok(Some(inscription_number));
|
||||
}
|
||||
Ok(None)
|
||||
@@ -428,7 +431,7 @@ pub fn find_inscription_with_id(
|
||||
while let Ok(Some(row)) = rows.next() {
|
||||
let inscription_block_hash: String = row.get(2).unwrap();
|
||||
if block_hash.eq(&inscription_block_hash) {
|
||||
let inscription_number: u64 = row.get(0).unwrap();
|
||||
let inscription_number: i64 = row.get(0).unwrap();
|
||||
let ordinal_number: u64 = row.get(1).unwrap();
|
||||
let traversal = TraversalResult {
|
||||
inscription_number,
|
||||
@@ -451,7 +454,7 @@ pub fn find_all_inscriptions(
|
||||
let mut results: BTreeMap<u64, Vec<(TransactionIdentifier, TraversalResult)>> = BTreeMap::new();
|
||||
let mut rows = stmt.query(args).unwrap();
|
||||
while let Ok(Some(row)) = rows.next() {
|
||||
let inscription_number: u64 = row.get(0).unwrap();
|
||||
let inscription_number: i64 = row.get(0).unwrap();
|
||||
let ordinal_number: u64 = row.get(1).unwrap();
|
||||
let block_height: u64 = row.get(2).unwrap();
|
||||
let transaction_id = {
|
||||
@@ -476,7 +479,7 @@ pub fn find_all_inscriptions(
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WatchedSatpoint {
|
||||
pub inscription_id: String,
|
||||
pub inscription_number: u64,
|
||||
pub inscription_number: i64,
|
||||
pub ordinal_number: u64,
|
||||
pub offset: u64,
|
||||
}
|
||||
@@ -503,7 +506,7 @@ pub fn find_watched_satpoint_for_inscription(
|
||||
.map_err(|e| format!("unable to query inscriptions table: {}", e.to_string()))?;
|
||||
while let Ok(Some(row)) = rows.next() {
|
||||
let inscription_id: String = row.get(0).unwrap();
|
||||
let inscription_number: u64 = row.get(1).unwrap();
|
||||
let inscription_number: i64 = row.get(1).unwrap();
|
||||
let ordinal_number: u64 = row.get(2).unwrap();
|
||||
let offset: u64 = row.get(3).unwrap();
|
||||
let block_height: u64 = row.get(4).unwrap();
|
||||
@@ -537,7 +540,7 @@ pub fn find_inscriptions_at_wached_outpoint(
|
||||
.map_err(|e| format!("unable to query inscriptions table: {}", e.to_string()))?;
|
||||
while let Ok(Some(row)) = rows.next() {
|
||||
let inscription_id: String = row.get(0).unwrap();
|
||||
let inscription_number: u64 = row.get(1).unwrap();
|
||||
let inscription_number: i64 = row.get(1).unwrap();
|
||||
let ordinal_number: u64 = row.get(2).unwrap();
|
||||
let offset: u64 = row.get(3).unwrap();
|
||||
results.push(WatchedSatpoint {
|
||||
@@ -813,7 +816,7 @@ pub async fn fetch_and_cache_blocks_in_hord_db(
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TraversalResult {
|
||||
pub inscription_number: u64,
|
||||
pub inscription_number: i64,
|
||||
pub ordinal_number: u64,
|
||||
pub transfers: u32,
|
||||
}
|
||||
@@ -834,7 +837,7 @@ pub fn retrieve_satoshi_point_using_lazy_storage(
|
||||
blocks_db: &DB,
|
||||
block_identifier: &BlockIdentifier,
|
||||
transaction_identifier: &TransactionIdentifier,
|
||||
inscription_number: u64,
|
||||
inscription_number: i64,
|
||||
traversals_cache: Arc<
|
||||
DashMap<(u32, [u8; 8]), LazyBlockTransaction, BuildHasherDefault<FxHasher>>,
|
||||
>,
|
||||
|
||||
@@ -36,21 +36,23 @@ use crate::{
|
||||
};
|
||||
|
||||
use self::db::{
|
||||
find_inscription_with_id, find_latest_inscription_number_at_block_height,
|
||||
open_readonly_hord_db_conn_rocks_db, remove_entry_from_blocks, remove_entry_from_inscriptions,
|
||||
LazyBlock, LazyBlockTransaction, TraversalResult, WatchedSatpoint,
|
||||
find_inscription_with_id, find_latest_cursed_inscription_number_at_block_height,
|
||||
find_latest_inscription_number_at_block_height, open_readonly_hord_db_conn_rocks_db,
|
||||
remove_entry_from_blocks, remove_entry_from_inscriptions, LazyBlock, LazyBlockTransaction,
|
||||
TraversalResult, WatchedSatpoint,
|
||||
};
|
||||
use self::inscription::InscriptionParser;
|
||||
use self::ord::inscription_id::InscriptionId;
|
||||
|
||||
pub fn try_parse_ordinal_operation(
|
||||
pub fn parse_ordinal_operations(
|
||||
tx: &BitcoinTransactionFullBreakdown,
|
||||
_block_height: u64,
|
||||
_ctx: &Context,
|
||||
) -> Option<OrdinalOperation> {
|
||||
) -> Vec<OrdinalOperation> {
|
||||
// This should eventually become a loop once/if there is settlement on https://github.com/casey/ord/issues/2000.
|
||||
if let Some(first_input) = tx.vin.get(0) {
|
||||
if let Some(ref witnesses) = first_input.txinwitness {
|
||||
let mut operations = vec![];
|
||||
for (input_index, input) in tx.vin.iter().enumerate() {
|
||||
if let Some(ref witnesses) = input.txinwitness {
|
||||
for bytes in witnesses.iter() {
|
||||
let script = Script::from(bytes.to_vec());
|
||||
let parser = InscriptionParser {
|
||||
@@ -64,7 +66,7 @@ pub fn try_parse_ordinal_operation(
|
||||
|
||||
let inscription_id = InscriptionId {
|
||||
txid: tx.txid.clone(),
|
||||
index: 0,
|
||||
index: input_index as u32,
|
||||
};
|
||||
|
||||
let inscription_output_value = tx
|
||||
@@ -85,27 +87,53 @@ pub fn try_parse_ordinal_operation(
|
||||
None
|
||||
};
|
||||
|
||||
return Some(OrdinalOperation::InscriptionRevealed(
|
||||
OrdinalInscriptionRevealData {
|
||||
content_type: inscription.content_type().unwrap_or("unknown").to_string(),
|
||||
content_bytes: format!("0x{}", hex::encode(&inscription_content_bytes)),
|
||||
content_length: inscription_content_bytes.len(),
|
||||
inscription_id: inscription_id.to_string(),
|
||||
inscriber_address,
|
||||
inscription_output_value,
|
||||
inscription_fee: 0,
|
||||
inscription_number: 0,
|
||||
ordinal_number: 0,
|
||||
ordinal_block_height: 0,
|
||||
ordinal_offset: 0,
|
||||
transfers_pre_inscription: 0,
|
||||
satpoint_post_inscription: format!("{}:0:0", tx.txid.clone()),
|
||||
},
|
||||
));
|
||||
if input_index == 0 {
|
||||
operations.push(OrdinalOperation::InscriptionRevealed(
|
||||
OrdinalInscriptionRevealData {
|
||||
content_type: inscription
|
||||
.content_type()
|
||||
.unwrap_or("unknown")
|
||||
.to_string(),
|
||||
content_bytes: format!("0x{}", hex::encode(&inscription_content_bytes)),
|
||||
content_length: inscription_content_bytes.len(),
|
||||
inscription_id: inscription_id.to_string(),
|
||||
inscriber_address,
|
||||
inscription_output_value,
|
||||
inscription_fee: 0,
|
||||
inscription_number: 0,
|
||||
ordinal_number: 0,
|
||||
ordinal_block_height: 0,
|
||||
ordinal_offset: 0,
|
||||
transfers_pre_inscription: 0,
|
||||
satpoint_post_inscription: format!("{}:0:0", tx.txid.clone()),
|
||||
},
|
||||
))
|
||||
} else {
|
||||
operations.push(OrdinalOperation::CursedInscriptionRevealed(
|
||||
OrdinalInscriptionRevealData {
|
||||
content_type: inscription
|
||||
.content_type()
|
||||
.unwrap_or("unknown")
|
||||
.to_string(),
|
||||
content_bytes: format!("0x{}", hex::encode(&inscription_content_bytes)),
|
||||
content_length: inscription_content_bytes.len(),
|
||||
inscription_id: inscription_id.to_string(),
|
||||
inscriber_address,
|
||||
inscription_output_value,
|
||||
inscription_fee: 0,
|
||||
inscription_number: 0,
|
||||
ordinal_number: 0,
|
||||
ordinal_block_height: 0,
|
||||
ordinal_offset: 0,
|
||||
transfers_pre_inscription: 0,
|
||||
satpoint_post_inscription: format!("{}:0:0", tx.txid.clone()),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
operations
|
||||
}
|
||||
|
||||
pub fn get_inscriptions_revealed_in_block(
|
||||
@@ -135,7 +163,8 @@ pub fn revert_hord_db_with_augmented_bitcoin_block(
|
||||
let tx = &block.transactions[block.transactions.len() - tx_index];
|
||||
for ordinal_event in tx.metadata.ordinal_operations.iter() {
|
||||
match ordinal_event {
|
||||
OrdinalOperation::InscriptionRevealed(data) => {
|
||||
OrdinalOperation::InscriptionRevealed(data)
|
||||
| OrdinalOperation::CursedInscriptionRevealed(data) => {
|
||||
// We remove any new inscription created
|
||||
remove_entry_from_inscriptions(
|
||||
&data.inscription_id,
|
||||
@@ -376,6 +405,27 @@ pub fn update_storage_and_augment_bitcoin_block_with_inscription_reveal_data(
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut latest_cursed_inscription_number =
|
||||
match find_latest_cursed_inscription_number_at_block_height(
|
||||
&block.block_identifier.index,
|
||||
&inscription_db_conn,
|
||||
&ctx,
|
||||
) {
|
||||
Ok(None) => -1,
|
||||
Ok(Some(inscription_number)) => inscription_number + 1,
|
||||
Err(e) => {
|
||||
ctx.try_log(|logger| {
|
||||
slog::error!(
|
||||
logger,
|
||||
"unable to retrieve inscription number: {}",
|
||||
e.to_string()
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut sats_overflow = vec![];
|
||||
|
||||
for new_tx in block.transactions.iter_mut().skip(1) {
|
||||
@@ -384,90 +434,105 @@ pub fn update_storage_and_augment_bitcoin_block_with_inscription_reveal_data(
|
||||
for (ordinal_event_index, ordinal_event) in
|
||||
new_tx.metadata.ordinal_operations.iter_mut().enumerate()
|
||||
{
|
||||
if let OrdinalOperation::InscriptionRevealed(inscription) = ordinal_event {
|
||||
let inscription_number = latest_inscription_number;
|
||||
let traversal = match traversals.get(&new_tx.transaction_identifier) {
|
||||
Some(traversal) => traversal,
|
||||
None => {
|
||||
ctx.try_log(|logger| {
|
||||
slog::info!(
|
||||
logger,
|
||||
"Unable to retrieve cached inscription data for inscription {}",
|
||||
new_tx.transaction_identifier.hash
|
||||
);
|
||||
});
|
||||
ordinals_events_indexes_to_discard.push_front(ordinal_event_index);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
inscription.ordinal_offset = traversal.get_ordinal_coinbase_offset();
|
||||
inscription.ordinal_block_height = traversal.get_ordinal_coinbase_height();
|
||||
inscription.ordinal_number = traversal.ordinal_number;
|
||||
inscription.inscription_number = traversal.inscription_number;
|
||||
inscription.transfers_pre_inscription = traversal.transfers;
|
||||
inscription.inscription_fee = new_tx.metadata.fee;
|
||||
let (inscription, is_cursed) = match ordinal_event {
|
||||
OrdinalOperation::InscriptionRevealed(inscription) => (inscription, false),
|
||||
OrdinalOperation::CursedInscriptionRevealed(inscription) => (inscription, true),
|
||||
OrdinalOperation::InscriptionTransferred(_) => continue,
|
||||
};
|
||||
|
||||
match storage {
|
||||
Storage::Sqlite(rw_hord_db_conn) => {
|
||||
if traversal.ordinal_number > 0 {
|
||||
if let Some(_entry) = find_inscription_with_ordinal_number(
|
||||
&traversal.ordinal_number,
|
||||
&inscription_db_conn,
|
||||
&ctx,
|
||||
) {
|
||||
ctx.try_log(|logger| {
|
||||
slog::warn!(
|
||||
logger,
|
||||
"Transaction {} in block {} is overriding an existing inscription {}",
|
||||
new_tx.transaction_identifier.hash,
|
||||
block.block_identifier.index,
|
||||
traversal.ordinal_number
|
||||
);
|
||||
});
|
||||
ordinals_events_indexes_to_discard.push_front(ordinal_event_index);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// If the satoshi inscribed correspond to a sat overflow, we will store the inscription
|
||||
// but exclude it from the block data
|
||||
sats_overflow.push(inscription.clone());
|
||||
let inscription_number = if is_cursed {
|
||||
latest_cursed_inscription_number
|
||||
} else {
|
||||
latest_inscription_number
|
||||
};
|
||||
|
||||
let traversal = match traversals.get(&new_tx.transaction_identifier) {
|
||||
Some(traversal) => traversal,
|
||||
None => {
|
||||
ctx.try_log(|logger| {
|
||||
slog::info!(
|
||||
logger,
|
||||
"Unable to retrieve cached inscription data for inscription {}",
|
||||
new_tx.transaction_identifier.hash
|
||||
);
|
||||
});
|
||||
ordinals_events_indexes_to_discard.push_front(ordinal_event_index);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
inscription.ordinal_offset = traversal.get_ordinal_coinbase_offset();
|
||||
inscription.ordinal_block_height = traversal.get_ordinal_coinbase_height();
|
||||
inscription.ordinal_number = traversal.ordinal_number;
|
||||
inscription.inscription_number = traversal.inscription_number;
|
||||
inscription.transfers_pre_inscription = traversal.transfers;
|
||||
inscription.inscription_fee = new_tx.metadata.fee;
|
||||
|
||||
match storage {
|
||||
Storage::Sqlite(rw_hord_db_conn) => {
|
||||
if traversal.ordinal_number > 0 {
|
||||
if let Some(_entry) = find_inscription_with_ordinal_number(
|
||||
&traversal.ordinal_number,
|
||||
&inscription_db_conn,
|
||||
&ctx,
|
||||
) {
|
||||
ctx.try_log(|logger| {
|
||||
slog::warn!(
|
||||
logger,
|
||||
"Transaction {} in block {} is overriding an existing inscription {}",
|
||||
new_tx.transaction_identifier.hash,
|
||||
block.block_identifier.index,
|
||||
traversal.ordinal_number
|
||||
);
|
||||
});
|
||||
ordinals_events_indexes_to_discard.push_front(ordinal_event_index);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// If the satoshi inscribed correspond to a sat overflow, we will store the inscription
|
||||
// but exclude it from the block data
|
||||
sats_overflow.push(inscription.clone());
|
||||
ordinals_events_indexes_to_discard.push_front(ordinal_event_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
if is_cursed {
|
||||
latest_cursed_inscription_number -= 1;
|
||||
} else {
|
||||
latest_inscription_number += 1;
|
||||
inscription.inscription_number = inscription_number;
|
||||
ctx.try_log(|logger| {
|
||||
slog::info!(
|
||||
logger,
|
||||
"Inscription {} (#{}) detected on Satoshi {} (block {}, {} transfers)",
|
||||
inscription.inscription_id,
|
||||
inscription.inscription_number,
|
||||
inscription.ordinal_number,
|
||||
block.block_identifier.index,
|
||||
inscription.transfers_pre_inscription,
|
||||
);
|
||||
});
|
||||
store_new_inscription(
|
||||
&inscription,
|
||||
&block.block_identifier,
|
||||
&rw_hord_db_conn,
|
||||
&ctx,
|
||||
};
|
||||
|
||||
inscription.inscription_number = inscription_number;
|
||||
ctx.try_log(|logger| {
|
||||
slog::info!(
|
||||
logger,
|
||||
"Inscription {} (#{}) detected on Satoshi {} (block {}, {} transfers)",
|
||||
inscription.inscription_id,
|
||||
inscription.inscription_number,
|
||||
inscription.ordinal_number,
|
||||
block.block_identifier.index,
|
||||
inscription.transfers_pre_inscription,
|
||||
);
|
||||
}
|
||||
Storage::Memory(map) => {
|
||||
let outpoint = inscription.satpoint_post_inscription
|
||||
[0..inscription.satpoint_post_inscription.len() - 2]
|
||||
.to_string();
|
||||
map.insert(
|
||||
outpoint,
|
||||
vec![WatchedSatpoint {
|
||||
inscription_id: inscription.inscription_id.clone(),
|
||||
inscription_number: inscription.inscription_number,
|
||||
ordinal_number: inscription.ordinal_number,
|
||||
offset: 0,
|
||||
}],
|
||||
);
|
||||
}
|
||||
});
|
||||
store_new_inscription(
|
||||
&inscription,
|
||||
&block.block_identifier,
|
||||
&rw_hord_db_conn,
|
||||
&ctx,
|
||||
);
|
||||
}
|
||||
Storage::Memory(map) => {
|
||||
let outpoint = inscription.satpoint_post_inscription
|
||||
[0..inscription.satpoint_post_inscription.len() - 2]
|
||||
.to_string();
|
||||
map.insert(
|
||||
outpoint,
|
||||
vec![WatchedSatpoint {
|
||||
inscription_id: inscription.inscription_id.clone(),
|
||||
inscription_number: inscription.inscription_number,
|
||||
ordinal_number: inscription.ordinal_number,
|
||||
offset: 0,
|
||||
}],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::chainhooks::types::{
|
||||
get_canonical_pox_config, get_stacks_canonical_magic_bytes, PoxConfig, StacksOpcodes,
|
||||
};
|
||||
|
||||
use crate::hord;
|
||||
use crate::observer::BitcoinConfig;
|
||||
use crate::utils::Context;
|
||||
use bitcoincore_rpc::bitcoin::hashes::hex::FromHex;
|
||||
@@ -23,7 +24,6 @@ use chainhook_types::{
|
||||
StacksBlockCommitmentData, TransactionIdentifier, TransferSTXData,
|
||||
};
|
||||
use hiro_system_kit::slog;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
|
||||
@@ -355,9 +355,7 @@ pub fn standardize_bitcoin_block(
|
||||
let mut ordinal_operations = vec![];
|
||||
|
||||
#[cfg(feature = "ordinals")]
|
||||
if let Some(op) = crate::hord::try_parse_ordinal_operation(&tx, block_height, ctx) {
|
||||
ordinal_operations.push(op);
|
||||
}
|
||||
ordinal_operations.append(&mut hord::parse_ordinal_operations(&tx, block_height, ctx));
|
||||
|
||||
let mut inputs = vec![];
|
||||
let mut sats_in = 0;
|
||||
|
||||
@@ -303,12 +303,13 @@ pub struct BitcoinTransactionMetadata {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum OrdinalOperation {
|
||||
InscriptionRevealed(OrdinalInscriptionRevealData),
|
||||
CursedInscriptionRevealed(OrdinalInscriptionRevealData),
|
||||
InscriptionTransferred(OrdinalInscriptionTransferData),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct OrdinalInscriptionTransferData {
|
||||
pub inscription_number: u64,
|
||||
pub inscription_number: i64,
|
||||
pub inscription_id: String,
|
||||
pub ordinal_number: u64,
|
||||
pub updated_address: Option<String>,
|
||||
@@ -322,7 +323,7 @@ pub struct OrdinalInscriptionRevealData {
|
||||
pub content_bytes: String,
|
||||
pub content_type: String,
|
||||
pub content_length: usize,
|
||||
pub inscription_number: u64,
|
||||
pub inscription_number: i64,
|
||||
pub inscription_fee: u64,
|
||||
pub inscription_output_value: u64,
|
||||
pub inscription_id: String,
|
||||
|
||||
Reference in New Issue
Block a user