mirror of
https://github.com/alexgo-io/bitcoin-indexer.git
synced 2026-01-12 08:34:17 +08:00
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:
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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?;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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)),
|
||||||
¶ms,
|
¶ms,
|
||||||
)
|
)
|
||||||
.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()
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE inscriptions ADD COLUMN unbound_sequence BIGINT UNIQUE;
|
||||||
Reference in New Issue
Block a user