feat: attempt to support cursed inscriptions

This commit is contained in:
Ludo Galabru
2023-05-25 07:09:35 -04:00
parent 96825c35a8
commit 9b45f908b8
4 changed files with 194 additions and 127 deletions

View File

@@ -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>>,
>,

View File

@@ -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,
}],
);
}
}
}

View File

@@ -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;

View File

@@ -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,