fix: display unbound inscription satpoints as all zeros with unbound sequence as offset (#445)

* feat: start indexing brc20 balance history

* fix: api support

* style: revert

* remove extra

* track unbound seq

* test sequence

* remove old coinbase calculations
This commit is contained in:
Rafael Cárdenas
2025-02-21 09:31:39 -06:00
committed by GitHub
parent 41438aca96
commit 68158786f0
12 changed files with 247 additions and 105 deletions

View File

@@ -9,7 +9,7 @@ use bitcoincore_rpc::jsonrpc::error::RpcError;
use bitcoincore_rpc_json::GetRawTransactionResultVoutScriptPubKey; use bitcoincore_rpc_json::GetRawTransactionResultVoutScriptPubKey;
use chainhook_types::bitcoin::{OutPoint, TxIn, TxOut}; use chainhook_types::bitcoin::{OutPoint, TxIn, TxOut};
use chainhook_types::{ use chainhook_types::{
BitcoinBlockData, BitcoinBlockMetadata, BitcoinBlockSignaling, BitcoinNetwork, BitcoinBlockData, BitcoinBlockMetadata, BitcoinNetwork,
BitcoinTransactionData,BitcoinTransactionMetadata, BlockHeader, BlockIdentifier, BitcoinTransactionData,BitcoinTransactionMetadata, BlockHeader, BlockIdentifier,
TransactionIdentifier, TransactionIdentifier,
}; };

View File

@@ -63,6 +63,7 @@ pub struct OrdinalInscriptionRevealData {
pub satpoint_post_inscription: String, pub satpoint_post_inscription: String,
pub curse_type: Option<OrdinalInscriptionCurseType>, pub curse_type: Option<OrdinalInscriptionCurseType>,
pub charms: u16, pub charms: u16,
pub unbound_sequence: Option<i64>,
} }
impl OrdinalInscriptionNumber { impl OrdinalInscriptionNumber {

View File

@@ -89,6 +89,7 @@ impl Brc20RevealBuilder {
"9bb2314d666ae0b1db8161cb373fcc1381681f71445c4e0335aa80ea9c37fcdd:0:0".to_string(), "9bb2314d666ae0b1db8161cb373fcc1381681f71445c4e0335aa80ea9c37fcdd:0:0".to_string(),
curse_type: None, curse_type: None,
charms: 0, charms: 0,
unbound_sequence: None,
} }
} }
} }

View File

@@ -103,6 +103,7 @@ pub fn parse_inscriptions_from_witness(
satpoint_post_inscription: format!(""), satpoint_post_inscription: format!(""),
curse_type, curse_type,
charms: 0, charms: 0,
unbound_sequence: None,
}; };
inscriptions.push((reveal_data, envelope.payload)); inscriptions.push((reveal_data, envelope.payload));
} }

View File

@@ -16,6 +16,7 @@ use dashmap::DashMap;
use deadpool_postgres::Transaction; use deadpool_postgres::Transaction;
use fxhash::FxHasher; use fxhash::FxHasher;
use crate::core::protocol::satoshi_tracking::UNBOUND_INSCRIPTION_SATPOINT;
use crate::{ use crate::{
config::Config, config::Config,
core::resolve_absolute_pointer, core::resolve_absolute_pointer,
@@ -23,7 +24,7 @@ use crate::{
try_debug, try_error, try_info, try_debug, try_error, try_info,
utils::format_inscription_id, utils::format_inscription_id,
}; };
use ord::{charm::Charm, height::Height, sat::Sat}; use ord::{charm::Charm, sat::Sat};
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
@@ -401,12 +402,9 @@ pub async fn update_block_inscriptions_with_consensus_sequence_data(
// Check if we've previously inscribed over any satoshi being inscribed to in this new block. This would be a reinscription. // Check if we've previously inscribed over any satoshi being inscribed to in this new block. This would be a reinscription.
let mut reinscriptions_data = let mut reinscriptions_data =
ordinals_pg::get_reinscriptions_for_block(inscriptions_data, db_tx).await?; ordinals_pg::get_reinscriptions_for_block(inscriptions_data, db_tx).await?;
// Keep a reference of inscribed satoshis that fall outside of this block's total sats. These would be unbound inscriptions. // Keep a reference of inscribed satoshis that will go towards miner fees. These would be unbound inscriptions.
let mut sat_overflows = VecDeque::new(); let mut sat_overflows = VecDeque::new();
let network = get_bitcoin_network(&block.metadata.network); let network = get_bitcoin_network(&block.metadata.network);
let coinbase_subsidy = Height(block.block_identifier.index as u32).subsidy();
let coinbase_tx = &block.transactions[0].clone();
let mut cumulated_fees = 0u64;
for (tx_index, tx) in block.transactions.iter_mut().enumerate() { for (tx_index, tx) in block.transactions.iter_mut().enumerate() {
update_tx_inscriptions_with_consensus_sequence_data( update_tx_inscriptions_with_consensus_sequence_data(
@@ -416,9 +414,6 @@ pub async fn update_block_inscriptions_with_consensus_sequence_data(
sequence_cursor, sequence_cursor,
&network, &network,
inscriptions_data, inscriptions_data,
coinbase_tx,
coinbase_subsidy,
&mut cumulated_fees,
&mut sat_overflows, &mut sat_overflows,
&mut reinscriptions_data, &mut reinscriptions_data,
db_tx, db_tx,
@@ -427,19 +422,28 @@ pub async fn update_block_inscriptions_with_consensus_sequence_data(
.await?; .await?;
} }
// Assign inscription numbers to remaining unbound inscriptions.
while let Some((tx_index, op_index)) = sat_overflows.pop_front() { while let Some((tx_index, op_index)) = sat_overflows.pop_front() {
let OrdinalOperation::InscriptionRevealed(ref mut inscription_data) = let OrdinalOperation::InscriptionRevealed(ref mut inscription_data) =
block.transactions[tx_index].metadata.ordinal_operations[op_index] block.transactions[tx_index].metadata.ordinal_operations[op_index]
else { else {
continue; continue;
}; };
let is_cursed = inscription_data.curse_type.is_some(); let is_cursed = inscription_data.curse_type.is_some();
let inscription_number = sequence_cursor let inscription_number = sequence_cursor
.pick_next(is_cursed, block.block_identifier.index, &network, db_tx) .pick_next(is_cursed, block.block_identifier.index, &network, db_tx)
.await?; .await?;
inscription_data.inscription_number = inscription_number; inscription_data.inscription_number = inscription_number;
sequence_cursor.increment(is_cursed, db_tx).await?; sequence_cursor.increment(is_cursed, db_tx).await?;
// Also assign an unbound sequence number and set outpoint to all zeros, just like `ord`.
let unbound_sequence = sequence_cursor.increment_unbound(db_tx).await?;
inscription_data.satpoint_post_inscription =
format!("{UNBOUND_INSCRIPTION_SATPOINT}:{unbound_sequence}");
inscription_data.ordinal_offset = unbound_sequence as u64;
inscription_data.unbound_sequence = Some(unbound_sequence);
try_info!( try_info!(
ctx, ctx,
"Unbound inscription {} (#{}) detected on Satoshi {} (block #{}, {} transfers)", "Unbound inscription {} (#{}) detected on Satoshi {} (block #{}, {} transfers)",
@@ -464,9 +468,6 @@ async fn update_tx_inscriptions_with_consensus_sequence_data(
sequence_cursor: &mut SequenceCursor, sequence_cursor: &mut SequenceCursor,
network: &Network, network: &Network,
inscriptions_data: &mut BTreeMap<(TransactionIdentifier, usize, u64), TraversalResult>, inscriptions_data: &mut BTreeMap<(TransactionIdentifier, usize, u64), TraversalResult>,
coinbase_tx: &BitcoinTransactionData,
coinbase_subsidy: u64,
cumulated_fees: &mut u64,
sats_overflows: &mut VecDeque<(usize, usize)>, sats_overflows: &mut VecDeque<(usize, usize)>,
reinscriptions_data: &mut HashMap<u64, String>, reinscriptions_data: &mut HashMap<u64, String>,
db_tx: &Transaction<'_>, db_tx: &Transaction<'_>,
@@ -559,16 +560,8 @@ async fn update_tx_inscriptions_with_consensus_sequence_data(
} }
} }
let (destination, satpoint_post_transfer, output_value) = compute_satpoint_post_transfer( let (destination, satpoint_post_transfer, output_value) =
&&*tx, compute_satpoint_post_transfer(&&*tx, input_index, relative_offset, network, ctx);
input_index,
relative_offset,
network,
coinbase_tx,
coinbase_subsidy,
cumulated_fees,
ctx,
);
inscription.satpoint_post_inscription = satpoint_post_transfer; inscription.satpoint_post_inscription = satpoint_post_transfer;
inscription_subindex += 1; inscription_subindex += 1;
@@ -637,11 +630,150 @@ mod test {
protocol::{satoshi_numbering::TraversalResult, sequence_cursor::SequenceCursor}, protocol::{satoshi_numbering::TraversalResult, sequence_cursor::SequenceCursor},
test_builders::{TestBlockBuilder, TestTransactionBuilder}, test_builders::{TestBlockBuilder, TestTransactionBuilder},
}, },
db::{ordinals_pg, pg_reset_db, pg_test_connection, pg_test_connection_pool}, db::{
ordinals_pg::{self, insert_block},
pg_reset_db, pg_test_connection, pg_test_connection_pool,
},
}; };
use super::update_block_inscriptions_with_consensus_sequence_data; use super::update_block_inscriptions_with_consensus_sequence_data;
#[test_case(None => Ok(("0000000000000000000000000000000000000000000000000000000000000000:0:0".into(), Some(0))); "first unbound sequence")]
#[test_case(Some(230) => Ok(("0000000000000000000000000000000000000000000000000000000000000000:0:231".into(), Some(231))); "next unbound sequence")]
#[tokio::test]
async fn unbound_inscription_sequence(
curr_sequence: Option<i64>,
) -> Result<(String, Option<i64>), String> {
let ctx = Context::empty();
let mut sequence_cursor = SequenceCursor::new();
let mut cache_l1 = BTreeMap::new();
let tx_id = TransactionIdentifier {
hash: "0xb4722ad74e7092a194e367f2ec0609994ef7a006db4f9b9d055b46cfb6514e06".into(),
};
let input_index = 1;
cache_l1.insert(
(tx_id.clone(), input_index, 0),
TraversalResult {
inscription_number: OrdinalInscriptionNumber {
classic: 0,
jubilee: 0,
},
inscription_input_index: input_index,
transaction_identifier_inscription: tx_id.clone(),
ordinal_number: 817263817263,
transfers: 0,
},
);
let mut pg_client = pg_test_connection().await;
ordinals_pg::migrate(&mut pg_client).await?;
let result = {
let mut ord_client = pg_pool_client(&pg_test_connection_pool()).await?;
let client = pg_begin(&mut ord_client).await?;
if let Some(curr_sequence) = curr_sequence {
// Simulate previous unbound sequence
let mut tx = TestTransactionBuilder::new_with_operation().build();
if let OrdinalOperation::InscriptionRevealed(data) =
&mut tx.metadata.ordinal_operations[0]
{
data.unbound_sequence = Some(curr_sequence);
};
let block = TestBlockBuilder::new().transactions(vec![tx]).build();
insert_block(&block, &client).await?;
}
// Insert new block
let mut block = TestBlockBuilder::new()
.height(878878)
// Coinbase
.add_transaction(TestTransactionBuilder::new().build())
.add_transaction(
TestTransactionBuilder::new()
.hash(tx_id.hash.clone())
// Normal input
.add_input(TxIn {
previous_output: OutPoint {
txid: TransactionIdentifier { hash: "0xf181aa98f2572879bd02278c72c83c7eaac2db82af713d1d239fc41859b2a26e".into() },
vout: 0,
value: 8000,
block_height: 884200,
},
script_sig: "0x00".into(),
sequence: 0,
witness: vec!["0x00".into()],
})
// Goes to fees
.add_input(TxIn {
previous_output: OutPoint {
txid: TransactionIdentifier { hash: "0xf181aa98f2572879bd02278c72c83c7eaac2db82af713d1d239fc41859b2a26e".into() },
vout: 1,
value: 250,
block_height: 884200,
},
script_sig: "0x00".into(),
sequence: 0,
witness: vec!["0x00".into()],
})
.add_output(TxOut { value: 8000, script_pubkey: "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into() })
.add_ordinal_operation(OrdinalOperation::InscriptionRevealed(
OrdinalInscriptionRevealData {
content_bytes: "0x101010".into(),
content_type: "text/plain".into(),
content_length: 3,
inscription_number: OrdinalInscriptionNumber {
classic: 0,
jubilee: 0,
},
inscription_fee: 0,
inscription_output_value: 0,
inscription_id: "".into(),
inscription_input_index: input_index,
inscription_pointer: Some(8000),
inscriber_address: Some("bc1pd99n363yjz8gd2zhy7gstsmk4qkdz4t029j44wewhmee3dta429sm5xqrd".into()),
delegate: None,
metaprotocol: None,
metadata: None,
parents: vec![],
ordinal_number: 0,
ordinal_block_height: 0,
ordinal_offset: 0,
tx_index: 1,
transfers_pre_inscription: 0,
satpoint_post_inscription: "".into(),
curse_type: Some(OrdinalInscriptionCurseType::DuplicateField),
charms: 0,
unbound_sequence: None,
},
))
.build(),
)
.build();
update_block_inscriptions_with_consensus_sequence_data(
&mut block,
&mut sequence_cursor,
&mut cache_l1,
&client,
&ctx,
)
.await?;
let result = &block.transactions[1].metadata.ordinal_operations[0];
let data = match result {
OrdinalOperation::InscriptionRevealed(data) => data,
_ => unreachable!(),
};
Ok((
data.satpoint_post_inscription.clone(),
data.unbound_sequence,
))
};
pg_reset_db(&mut pg_client).await?;
result
}
#[test_case((884207, false, 1262349832364434, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![]); "common sat")] #[test_case((884207, false, 1262349832364434, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![]); "common sat")]
#[test_case((884207, false, 0, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![Charm::Coin, Charm::Mythic, Charm::Palindrome]); "mythic sat")] #[test_case((884207, false, 0, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![Charm::Coin, Charm::Mythic, Charm::Palindrome]); "mythic sat")]
#[test_case((884207, false, 1050000000000000, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![Charm::Coin, Charm::Epic]); "epic sat")] #[test_case((884207, false, 1050000000000000, "0x5120694b38ea24908e86a857279105c376a82cd1556f51655abb2ebef398b57daa8b".into()) => Ok(vec![Charm::Coin, Charm::Epic]); "epic sat")]
@@ -727,6 +859,7 @@ mod test {
satpoint_post_inscription: "".into(), satpoint_post_inscription: "".into(),
curse_type: if cursed { Some(OrdinalInscriptionCurseType::Generic) } else { None }, curse_type: if cursed { Some(OrdinalInscriptionCurseType::Generic) } else { None },
charms: 0, charms: 0,
unbound_sequence: None,
}, },
)) ))
.build(), .build(),
@@ -743,12 +876,10 @@ mod test {
.await?; .await?;
let result = &block.transactions[1].metadata.ordinal_operations[0]; let result = &block.transactions[1].metadata.ordinal_operations[0];
// println!("{:?}", result);
let charms = match result { let charms = match result {
OrdinalOperation::InscriptionRevealed(data) => data.charms, OrdinalOperation::InscriptionRevealed(data) => data.charms,
_ => unreachable!(), _ => unreachable!(),
}; };
// println!("{:?}", Charm::charms(charms));
Ok(Charm::charms(charms)) Ok(Charm::charms(charms))
}; };
pg_reset_db(&mut pg_client).await?; pg_reset_db(&mut pg_client).await?;

View File

@@ -14,10 +14,12 @@ use crate::{
try_info, try_info,
utils::format_outpoint_to_watch, utils::format_outpoint_to_watch,
}; };
use ord::height::Height;
use super::inscription_sequencing::get_bitcoin_network; use super::inscription_sequencing::get_bitcoin_network;
pub const UNBOUND_INSCRIPTION_SATPOINT: &str =
"0000000000000000000000000000000000000000000000000000000000000000:0";
#[derive(Clone, Debug, Ord, PartialOrd, PartialEq, Eq)] #[derive(Clone, Debug, Ord, PartialOrd, PartialEq, Eq)]
pub struct WatchedSatpoint { pub struct WatchedSatpoint {
pub ordinal_number: u64, pub ordinal_number: u64,
@@ -30,7 +32,7 @@ pub fn parse_output_and_offset_from_satpoint(
let parts: Vec<&str> = satpoint.split(':').collect(); let parts: Vec<&str> = satpoint.split(':').collect();
let tx_id = parts let tx_id = parts
.get(0) .get(0)
.ok_or("get_output_and_offset_from_satpoint: inscription_id not found")?; .ok_or("get_output_and_offset_from_satpoint: tx_id not found")?;
let output = parts let output = parts
.get(1) .get(1)
.ok_or("get_output_and_offset_from_satpoint: output not found")?; .ok_or("get_output_and_offset_from_satpoint: output not found")?;
@@ -50,18 +52,12 @@ pub async fn augment_block_with_transfers(
ctx: &Context, ctx: &Context,
) -> Result<(), String> { ) -> Result<(), String> {
let network = get_bitcoin_network(&block.metadata.network); let network = get_bitcoin_network(&block.metadata.network);
let coinbase_subsidy = Height(block.block_identifier.index as u32).subsidy();
let coinbase_tx = &block.transactions[0].clone();
let mut cumulated_fees = 0;
for (tx_index, tx) in block.transactions.iter_mut().enumerate() { for (tx_index, tx) in block.transactions.iter_mut().enumerate() {
let _ = augment_transaction_with_ordinal_transfers( let _ = augment_transaction_with_ordinal_transfers(
tx, tx,
tx_index, tx_index,
&block.block_identifier, &block.block_identifier,
&network, &network,
&coinbase_tx,
coinbase_subsidy,
&mut cumulated_fees,
db_tx, db_tx,
ctx, ctx,
) )
@@ -75,9 +71,6 @@ pub fn compute_satpoint_post_transfer(
input_index: usize, input_index: usize,
relative_pointer_value: u64, relative_pointer_value: u64,
network: &Network, network: &Network,
coinbase_tx: &BitcoinTransactionData,
coinbase_subsidy: u64,
cumulated_fees: &mut u64,
ctx: &Context, ctx: &Context,
) -> (OrdinalInscriptionTransferDestination, String, Option<u64>) { ) -> (OrdinalInscriptionTransferDestination, String, Option<u64>) {
let inputs: Vec<u64> = tx let inputs: Vec<u64> = tx
@@ -131,37 +124,11 @@ pub fn compute_satpoint_post_transfer(
Some(tx.metadata.outputs[output_index].value), Some(tx.metadata.outputs[output_index].value),
) )
} }
SatPosition::Fee(offset) => { SatPosition::Fee(_) => {
// Get Coinbase TX // Unbound inscription satpoints will be updated later with an unbound sequence number.
let total_offset = coinbase_subsidy + *cumulated_fees + offset;
let outputs = coinbase_tx
.metadata
.outputs
.iter()
.map(|o| o.value)
.collect();
let post_transfer_data = compute_next_satpoint_data(
0,
&vec![total_offset],
&outputs,
total_offset,
Some(ctx),
);
// Identify the correct output
let (output_index, offset) = match post_transfer_data {
SatPosition::Output(pos) => pos,
_ => {
try_info!(ctx, "unable to locate satoshi in coinbase outputs");
(0, total_offset)
}
};
let outpoint =
format_outpoint_to_watch(&coinbase_tx.transaction_identifier, output_index);
( (
outpoint, UNBOUND_INSCRIPTION_SATPOINT.into(),
offset, 0,
OrdinalInscriptionTransferDestination::SpentInFees, OrdinalInscriptionTransferDestination::SpentInFees,
None, None,
) )
@@ -181,9 +148,6 @@ pub async fn augment_transaction_with_ordinal_transfers(
tx_index: usize, tx_index: usize,
block_identifier: &BlockIdentifier, block_identifier: &BlockIdentifier,
network: &Network, network: &Network,
coinbase_tx: &BitcoinTransactionData,
coinbase_subsidy: u64,
cumulated_fees: &mut u64,
db_tx: &Transaction<'_>, db_tx: &Transaction<'_>,
ctx: &Context, ctx: &Context,
) -> Result<Vec<OrdinalInscriptionTransferData>, String> { ) -> Result<Vec<OrdinalInscriptionTransferData>, String> {
@@ -224,9 +188,6 @@ pub async fn augment_transaction_with_ordinal_transfers(
input_index, input_index,
watched_satpoint.offset, watched_satpoint.offset,
network, network,
coinbase_tx,
coinbase_subsidy,
cumulated_fees,
ctx, ctx,
); );
@@ -253,7 +214,6 @@ pub async fn augment_transaction_with_ordinal_transfers(
.push(OrdinalOperation::InscriptionTransferred(transfer_data)); .push(OrdinalOperation::InscriptionTransferred(transfer_data));
} }
} }
*cumulated_fees += tx.metadata.fee;
Ok(transfers) Ok(transfers)
} }
@@ -275,21 +235,10 @@ mod test {
.add_input(TestTxInBuilder::new().value(10_000).build()) .add_input(TestTxInBuilder::new().value(10_000).build())
.add_output(TestTxOutBuilder::new().value(2_000).build()) .add_output(TestTxOutBuilder::new().value(2_000).build())
.build(); .build();
let coinbase_tx = &TestTransactionBuilder::new()
.add_output(TestTxOutBuilder::new().value(312_500_000 + 5_000).build())
.build();
let (destination, satpoint, value) = compute_satpoint_post_transfer( // This 5000 offset will make it go to fees.
tx, let (destination, satpoint, value) =
0, compute_satpoint_post_transfer(tx, 0, 5_000, &Network::Bitcoin, &ctx);
// This offset will make it go to fees.
5_000,
&Network::Bitcoin,
coinbase_tx,
312_500_000,
&mut 0,
&ctx,
);
assert_eq!( assert_eq!(
destination, destination,
@@ -297,8 +246,7 @@ mod test {
); );
assert_eq!( assert_eq!(
satpoint, satpoint,
"b61b0172d95e266c18aea0c624db987e971a5d6d4ebc2aaed85da4642d635735:0:312503000" "0000000000000000000000000000000000000000000000000000000000000000:0:0"
.to_string()
); );
assert_eq!(value, None); assert_eq!(value, None);
} }
@@ -316,20 +264,9 @@ mod test {
.build() .build()
) )
.build(); .build();
let coinbase_tx = &TestTransactionBuilder::new()
.add_output(TestTxOutBuilder::new().value(312_500_000).build())
.build();
let (destination, satpoint, value) = compute_satpoint_post_transfer( let (destination, satpoint, value) =
tx, compute_satpoint_post_transfer(tx, 0, 5_000, &Network::Bitcoin, &ctx);
0,
5_000,
&Network::Bitcoin,
coinbase_tx,
312_500_000,
&mut 0,
&ctx,
);
assert_eq!( assert_eq!(
destination, destination,

View File

@@ -16,6 +16,7 @@ pub struct SequenceCursor {
pos_cursor: Option<i64>, pos_cursor: Option<i64>,
neg_cursor: Option<i64>, neg_cursor: Option<i64>,
jubilee_cursor: Option<i64>, jubilee_cursor: Option<i64>,
unbound_cursor: Option<i64>,
current_block_height: u64, current_block_height: u64,
} }
@@ -25,6 +26,7 @@ impl SequenceCursor {
jubilee_cursor: None, jubilee_cursor: None,
pos_cursor: None, pos_cursor: None,
neg_cursor: None, neg_cursor: None,
unbound_cursor: None,
current_block_height: 0, current_block_height: 0,
} }
} }
@@ -33,6 +35,7 @@ impl SequenceCursor {
self.pos_cursor = None; self.pos_cursor = None;
self.neg_cursor = None; self.neg_cursor = None;
self.jubilee_cursor = None; self.jubilee_cursor = None;
self.unbound_cursor = None;
self.current_block_height = 0; self.current_block_height = 0;
} }
@@ -76,6 +79,12 @@ impl SequenceCursor {
Ok(()) Ok(())
} }
pub async fn increment_unbound<T: GenericClient>(&mut self, client: &T) -> Result<i64, String> {
let next = self.pick_next_unbound(client).await?;
self.unbound_cursor = Some(next);
Ok(next)
}
async fn pick_next_pos_classic<T: GenericClient>(&mut self, client: &T) -> Result<i64, String> { async fn pick_next_pos_classic<T: GenericClient>(&mut self, client: &T) -> Result<i64, String> {
match self.pos_cursor { match self.pos_cursor {
None => { None => {
@@ -122,6 +131,19 @@ impl SequenceCursor {
} }
} }
async fn pick_next_unbound<T: GenericClient>(&mut self, client: &T) -> Result<i64, String> {
match self.unbound_cursor {
None => match ordinals_pg::get_highest_unbound_inscription_sequence(client).await? {
Some(unbound_sequence) => {
self.unbound_cursor = Some(unbound_sequence);
Ok(unbound_sequence + 1)
}
_ => Ok(0),
},
Some(value) => Ok(value + 1),
}
}
async fn increment_neg_classic<T: GenericClient>(&mut self, client: &T) -> Result<(), String> { async fn increment_neg_classic<T: GenericClient>(&mut self, client: &T) -> Result<(), String> {
self.neg_cursor = Some(self.pick_next_neg_classic(client).await?); self.neg_cursor = Some(self.pick_next_neg_classic(client).await?);
Ok(()) Ok(())
@@ -146,6 +168,7 @@ mod test {
use bitcoin::Network; use bitcoin::Network;
use chainhook_postgres::{pg_begin, pg_pool_client}; use chainhook_postgres::{pg_begin, pg_pool_client};
use chainhook_types::OrdinalOperation;
use test_case::test_case; use test_case::test_case;
use crate::{ use crate::{
@@ -204,4 +227,30 @@ mod test {
pg_reset_db(&mut pg_client).await?; pg_reset_db(&mut pg_client).await?;
Ok(result) Ok(result)
} }
#[test_case(None => Ok(0); "without sequence")]
#[test_case(Some(21) => Ok(22); "with current sequence")]
#[tokio::test]
async fn picks_next_unbound_sequence(curr_sequence: Option<i64>) -> Result<i64, String> {
let mut pg_client = pg_test_connection().await;
ordinals_pg::migrate(&mut pg_client).await?;
let result = {
let mut ord_client = pg_pool_client(&pg_test_connection_pool()).await?;
let client = pg_begin(&mut ord_client).await?;
let mut tx = TestTransactionBuilder::new_with_operation().build();
if let OrdinalOperation::InscriptionRevealed(data) =
&mut tx.metadata.ordinal_operations[0]
{
data.unbound_sequence = curr_sequence;
};
let block = TestBlockBuilder::new().transactions(vec![tx]).build();
insert_block(&block, &client).await?;
let mut cursor = SequenceCursor::new();
cursor.increment_unbound(&client).await?
};
pg_reset_db(&mut pg_client).await?;
Ok(result)
}
} }

View File

@@ -105,6 +105,7 @@ impl TestTransactionBuilder {
satpoint_post_inscription: "b61b0172d95e266c18aea0c624db987e971a5d6d4ebc2aaed85da4642d635735:0:0".to_string(), satpoint_post_inscription: "b61b0172d95e266c18aea0c624db987e971a5d6d4ebc2aaed85da4642d635735:0:0".to_string(),
curse_type: None, curse_type: None,
charms: 0, charms: 0,
unbound_sequence: None,
}, },
)]; )];
tx tx

View File

@@ -33,6 +33,7 @@ pub struct DbInscription {
pub delegate: Option<String>, pub delegate: Option<String>,
pub timestamp: PgBigIntU32, pub timestamp: PgBigIntU32,
pub charms: PgBigIntU32, pub charms: PgBigIntU32,
pub unbound_sequence: Option<i64>,
} }
impl DbInscription { impl DbInscription {
@@ -84,6 +85,7 @@ impl DbInscription {
delegate: reveal.delegate.clone(), delegate: reveal.delegate.clone(),
timestamp: PgBigIntU32(timestamp), timestamp: PgBigIntU32(timestamp),
charms: PgBigIntU32(reveal.charms as u32), charms: PgBigIntU32(reveal.charms as u32),
unbound_sequence: reveal.unbound_sequence,
} }
} }
} }
@@ -114,6 +116,7 @@ impl FromPgRow for DbInscription {
delegate: row.get("delegate"), delegate: row.get("delegate"),
timestamp: row.get("timestamp"), timestamp: row.get("timestamp"),
charms: row.get("charms"), charms: row.get("charms"),
unbound_sequence: row.get("unbound_sequence"),
} }
} }
} }

View File

@@ -62,6 +62,7 @@ mod test {
satpoint_post_inscription: "e47a70a218dfa746ba410b1c057403bb481523d830562fd8dec61ec4d2915e5f:0:0".to_string(), satpoint_post_inscription: "e47a70a218dfa746ba410b1c057403bb481523d830562fd8dec61ec4d2915e5f:0:0".to_string(),
curse_type: None, curse_type: None,
charms: 0, charms: 0,
unbound_sequence: None,
}; };
let recursions = DbInscriptionRecursion::from_reveal(&reveal).unwrap(); let recursions = DbInscriptionRecursion::from_reveal(&reveal).unwrap();
assert_eq!(2, recursions.len()); assert_eq!(2, recursions.len());

View File

@@ -98,6 +98,20 @@ pub async fn get_lowest_cursed_classic_inscription_number<T: GenericClient>(
Ok(min) Ok(min)
} }
pub async fn get_highest_unbound_inscription_sequence<T: GenericClient>(
client: &T,
) -> Result<Option<i64>, String> {
let row = client
.query_opt("SELECT MAX(unbound_sequence) AS max FROM inscriptions", &[])
.await
.map_err(|e| format!("get_highest_unbound_inscription_sequence: {e}"))?;
let Some(row) = row else {
return Ok(None);
};
let max: Option<i64> = row.get("max");
Ok(max)
}
pub async fn get_reinscriptions_for_block<T: GenericClient>( pub async fn get_reinscriptions_for_block<T: GenericClient>(
inscriptions_data: &mut BTreeMap<(TransactionIdentifier, usize, u64), TraversalResult>, inscriptions_data: &mut BTreeMap<(TransactionIdentifier, usize, u64), TraversalResult>,
client: &T, client: &T,
@@ -261,15 +275,16 @@ async fn insert_inscriptions<T: GenericClient>(
params.push(&row.delegate); params.push(&row.delegate);
params.push(&row.timestamp); params.push(&row.timestamp);
params.push(&row.charms); params.push(&row.charms);
params.push(&row.unbound_sequence);
} }
client client
.query( .query(
&format!("INSERT INTO inscriptions &format!("INSERT INTO inscriptions
(inscription_id, ordinal_number, number, classic_number, block_height, block_hash, tx_id, tx_index, address, (inscription_id, ordinal_number, number, classic_number, block_height, block_hash, tx_id, tx_index, address,
mime_type, content_type, content_length, content, fee, curse_type, recursive, input_index, pointer, metadata, mime_type, content_type, content_length, content, fee, curse_type, recursive, input_index, pointer, metadata,
metaprotocol, delegate, timestamp, charms) metaprotocol, delegate, timestamp, charms, unbound_sequence)
VALUES {} VALUES {}
ON CONFLICT (number) DO NOTHING", utils::multi_row_query_param_str(chunk.len(), 23)), ON CONFLICT (number) DO NOTHING", utils::multi_row_query_param_str(chunk.len(), 24)),
&params, &params,
) )
.await .await
@@ -1206,6 +1221,7 @@ mod test {
satpoint_post_inscription: "b61b0172d95e266c18aea0c624db987e971a5d6d4ebc2aaed85da4642d635735:0:0".to_string(), satpoint_post_inscription: "b61b0172d95e266c18aea0c624db987e971a5d6d4ebc2aaed85da4642d635735:0:0".to_string(),
curse_type: None, curse_type: None,
charms: 0, charms: 0,
unbound_sequence: None,
}, },
)) ))
.build() .build()

View File

@@ -0,0 +1 @@
ALTER TABLE inscriptions ADD COLUMN unbound_sequence BIGINT UNIQUE;