Skip to content

Commit

Permalink
Track inscription satpoints (ordinals#860)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Nov 30, 2022
1 parent 82296e4 commit 61f6f54
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 41 deletions.
58 changes: 44 additions & 14 deletions src/index.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use {
self::updater::Updater,
super::*,
bitcoin::consensus::encode::deserialize,
bitcoin::BlockHeader,
bitcoincore_rpc::{json::GetBlockHeaderResult, Auth, Client},
indicatif::{ProgressBar, ProgressStyle},
Expand Down Expand Up @@ -33,6 +32,10 @@ const INSCRIPTION_ID_TO_INSCRIPTION: TableDefinition<&InscriptionIdArray, str> =
TableDefinition::new("INSCRIPTION_ID_TO_INSCRIPTION");
const WRITE_TRANSACTION_STARTING_BLOCK_COUNT_TO_TIMESTAMP: TableDefinition<u64, u128> =
TableDefinition::new("WRITE_TRANSACTION_START_BLOCK_COUNT_TO_TIMESTAMP");
const INSCRIPTION_ID_TO_SATPOINT: TableDefinition<&InscriptionIdArray, &SatPointArray> =
TableDefinition::new("INSCRIPTION_ID_TO_SATPOINT");
const SATPOINT_TO_INSCRIPTION_ID: TableDefinition<&SatPointArray, &InscriptionIdArray> =
TableDefinition::new("SATPOINT_TO_INSCRIPTION_ID");

fn encode_outpoint(outpoint: OutPoint) -> OutPointArray {
let mut array = [0; 36];
Expand All @@ -50,6 +53,14 @@ fn encode_satpoint(satpoint: SatPoint) -> SatPointArray {
array
}

fn decode_satpoint(array: SatPointArray) -> SatPoint {
Decodable::consensus_decode(&mut io::Cursor::new(array)).unwrap()
}

fn decode_outpoint(array: OutPointArray) -> OutPoint {
Decodable::consensus_decode(&mut io::Cursor::new(array)).unwrap()
}

pub(crate) struct Index {
auth: Auth,
chain: Chain,
Expand Down Expand Up @@ -190,10 +201,12 @@ impl Index {
};

tx.open_table(HEIGHT_TO_BLOCK_HASH)?;
tx.open_table(INSCRIPTION_ID_TO_INSCRIPTION)?;
tx.open_table(INSCRIPTION_ID_TO_SATPOINT)?;
tx.open_table(ORDINAL_TO_INSCRIPTION_ID)?;
tx.open_table(ORDINAL_TO_SATPOINT)?;
tx.open_table(SATPOINT_TO_INSCRIPTION_ID)?;
tx.open_table(STATISTIC_TO_COUNT)?;
tx.open_table(INSCRIPTION_ID_TO_INSCRIPTION)?;
tx.open_table(WRITE_TRANSACTION_STARTING_BLOCK_COUNT_TO_TIMESTAMP)?;

if options.index_ordinals {
Expand Down Expand Up @@ -384,7 +397,7 @@ impl Index {
let ordinal_to_satpoint = rtx.open_table(ORDINAL_TO_SATPOINT)?;

for (ordinal, satpoint) in ordinal_to_satpoint.range(0..)? {
result.push((Ordinal(ordinal), deserialize(satpoint)?));
result.push((Ordinal(ordinal), decode_satpoint(*satpoint)));
}

Ok(result)
Expand Down Expand Up @@ -413,7 +426,7 @@ impl Index {
self.client.get_block(&hash).into_option()
}

pub(crate) fn inscription(&self, ordinal: Ordinal) -> Result<Option<Inscription>> {
pub(crate) fn get_inscription_by_ordinal(&self, ordinal: Ordinal) -> Result<Option<Inscription>> {
let db = self.database.begin_read()?;
let table = db.open_table(ORDINAL_TO_INSCRIPTION_ID)?;

Expand All @@ -434,18 +447,35 @@ impl Index {
)
}

pub(crate) fn inscription_from_txid(&self, txid: Txid) -> Result<Option<Inscription>> {
Ok(
self
pub(crate) fn get_inscription_by_inscription_id(
&self,
txid: Txid,
) -> Result<Option<(Inscription, SatPoint)>> {
let inscription = self
.database
.begin_read()?
.open_table(INSCRIPTION_ID_TO_INSCRIPTION)?
.get(txid.as_inner())?
.map(|inscription| {
serde_json::from_str(inscription)
.expect("failed to deserialize inscription (JSON) from database")
});

let inscription = match inscription {
Some(inscription) => inscription,
None => return Ok(None),
};

let satpoint = decode_satpoint(
*self
.database
.begin_read()?
.open_table(INSCRIPTION_ID_TO_INSCRIPTION)?
.open_table(INSCRIPTION_ID_TO_SATPOINT)?
.get(txid.as_inner())?
.map(|inscription| {
serde_json::from_str(inscription)
.expect("failed to deserialize inscription (JSON) from database")
}),
)
.ok_or_else(|| anyhow!("no satpoint for inscription"))?,
);

Ok(Some((inscription, satpoint)))
}

pub(crate) fn transaction(&self, txid: Txid) -> Result<Option<Transaction>> {
Expand Down Expand Up @@ -487,7 +517,7 @@ impl Index {
for chunk in value.chunks_exact(11) {
let (start, end) = Index::decode_ordinal_range(chunk.try_into().unwrap());
if start <= ordinal && ordinal < end {
let outpoint: OutPoint = deserialize(key.as_slice())?;
let outpoint = decode_outpoint(*key);
return Ok(Some(SatPoint {
outpoint,
offset: offset + ordinal - start,
Expand Down
82 changes: 68 additions & 14 deletions src/index/updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,14 @@ impl Updater {
}
}

let mut inscription_id_to_inscription = wtx.open_table(INSCRIPTION_ID_TO_INSCRIPTION)?;
let mut inscription_id_to_satpoint = wtx.open_table(INSCRIPTION_ID_TO_SATPOINT)?;
let mut satpoint_to_inscription_id = wtx.open_table(SATPOINT_TO_INSCRIPTION_ID)?;

if self.index_ordinals {
let mut ordinal_to_inscription_id = wtx.open_table(ORDINAL_TO_INSCRIPTION_ID)?;
let mut ordinal_to_satpoint = wtx.open_table(ORDINAL_TO_SATPOINT)?;
let mut outpoint_to_ordinal_ranges = wtx.open_table(OUTPOINT_TO_ORDINAL_RANGES)?;
let mut inscription_id_to_inscription = wtx.open_table(INSCRIPTION_ID_TO_INSCRIPTION)?;

let mut coinbase_inputs = VecDeque::new();

Expand Down Expand Up @@ -296,6 +299,8 @@ impl Updater {
&mut ordinal_to_satpoint,
&mut ordinal_to_inscription_id,
&mut inscription_id_to_inscription,
&mut inscription_id_to_satpoint,
&mut satpoint_to_inscription_id,
&mut input_ordinal_ranges,
&mut ordinal_ranges_written,
&mut outputs_in_block,
Expand All @@ -311,16 +316,21 @@ impl Updater {
&mut ordinal_to_satpoint,
&mut ordinal_to_inscription_id,
&mut inscription_id_to_inscription,
&mut inscription_id_to_satpoint,
&mut satpoint_to_inscription_id,
&mut coinbase_inputs,
&mut ordinal_ranges_written,
&mut outputs_in_block,
)?;
}
} else {
let mut inscription_id_to_inscription = wtx.open_table(INSCRIPTION_ID_TO_INSCRIPTION)?;

for tx in &block.txdata {
self.index_transaction_inscriptions(tx, &mut inscription_id_to_inscription)?
self.index_transaction_inscriptions(
tx,
&mut inscription_id_to_inscription,
&mut inscription_id_to_satpoint,
&mut satpoint_to_inscription_id,
)?;
}
}

Expand All @@ -341,15 +351,57 @@ impl Updater {
&mut self,
tx: &Transaction,
inscription_id_to_inscription: &mut Table<&InscriptionIdArray, str>,
) -> Result {
if let Some(inscription) = Inscription::from_transaction(tx) {
inscription_id_to_satpoint: &mut Table<&InscriptionIdArray, &SatPointArray>,
satpoint_to_inscription_id: &mut Table<&SatPointArray, &InscriptionIdArray>,
) -> Result<bool> {
let inscription = Inscription::from_transaction(tx);
if let Some(inscription) = &inscription {
let json = serde_json::to_string(&inscription)
.expect("Inscription serialization should always succeed");

inscription_id_to_inscription.insert(tx.txid().as_inner(), &json)?;
let txid = tx.txid();
let satpoint = encode_satpoint(SatPoint {
outpoint: OutPoint { txid, vout: 0 },
offset: 0,
});

inscription_id_to_inscription.insert(txid.as_inner(), &json)?;
inscription_id_to_satpoint.insert(txid.as_inner(), &satpoint)?;
satpoint_to_inscription_id.insert(&satpoint, txid.as_inner())?;
};

for tx_in in &tx.input {
let outpoint = tx_in.previous_output;
let start = encode_satpoint(SatPoint {
outpoint,
offset: 0,
});

let end = encode_satpoint(SatPoint {
outpoint,
offset: u64::MAX,
});

let inscription_ids: Vec<(SatPointArray, InscriptionIdArray)> = satpoint_to_inscription_id
.range(start..=end)?
.map(|(satpoint, id)| (*satpoint, *id))
.collect();

for (_old_satpoint, inscription_id) in inscription_ids {
let new_satpoint = encode_satpoint(SatPoint {
outpoint: OutPoint {
txid: tx.txid(),
vout: 0,
},
offset: 0,
});

satpoint_to_inscription_id.insert(&new_satpoint, &inscription_id)?;
inscription_id_to_satpoint.insert(&inscription_id, &new_satpoint)?;
}
}

Ok(())
Ok(inscription.is_some())
}

pub(crate) fn index_transaction_ordinals(
Expand All @@ -359,16 +411,18 @@ impl Updater {
ordinal_to_satpoint: &mut Table<u64, &SatPointArray>,
ordinal_to_inscription_id: &mut Table<u64, &InscriptionIdArray>,
inscription_id_to_inscription: &mut Table<&InscriptionIdArray, str>,
inscription_id_to_satpoint: &mut Table<&InscriptionIdArray, &SatPointArray>,
satpoint_to_inscription_id: &mut Table<&SatPointArray, &InscriptionIdArray>,
input_ordinal_ranges: &mut VecDeque<(u64, u64)>,
ordinal_ranges_written: &mut u64,
outputs_traversed: &mut u64,
) -> Result {
if let Some(inscription) = Inscription::from_transaction(tx) {
let json = serde_json::to_string(&inscription)
.expect("Inscription serialization should always succeed");

inscription_id_to_inscription.insert(tx.txid().as_inner(), &json)?;

if self.index_transaction_inscriptions(
tx,
inscription_id_to_inscription,
inscription_id_to_satpoint,
satpoint_to_inscription_id,
)? {
if let Some((start, _end)) = input_ordinal_ranges.get(0) {
ordinal_to_inscription_id.insert(&start, tx.txid().as_inner())?;
}
Expand Down
21 changes: 12 additions & 9 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ impl Server {
blocktime: index.blocktime(ordinal.height()).map_err(|err| {
ServerError::Internal(anyhow!("failed to retrieve blocktime from index: {err}"))
})?,
inscription: index.inscription(ordinal).map_err(|err| {
inscription: index.get_inscription_by_ordinal(ordinal).map_err(|err| {
ServerError::Internal(anyhow!(
"failed to retrieve inscription for ordinal {ordinal} from index: {err}"
))
Expand Down Expand Up @@ -613,17 +613,20 @@ impl Server {
Extension(index): Extension<Arc<Index>>,
Path(txid): Path<Txid>,
) -> ServerResult<PageHtml> {
let (inscription, satpoint) = index
.get_inscription_by_inscription_id(txid)
.map_err(|err| {
ServerError::Internal(anyhow!(
"failed to retrieve inscription from txid {txid} from index: {err}"
))
})?
.ok_or_else(|| ServerError::NotFound(format!("transaction {txid} has no inscription")))?;

Ok(
InscriptionHtml {
txid,
inscription: index
.inscription_from_txid(txid)
.map_err(|err| {
ServerError::Internal(anyhow!(
"failed to retrieve inscription from txid {txid} from index: {err}"
))
})?
.ok_or_else(|| ServerError::NotFound(format!("transaction {txid} has no inscription")))?,
inscription,
satpoint,
}
.page(
chain,
Expand Down
11 changes: 11 additions & 0 deletions src/subcommand/server/templates/inscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::*;
pub(crate) struct InscriptionHtml {
pub(crate) txid: Txid,
pub(crate) inscription: Inscription,
pub(crate) satpoint: SatPoint,
}

impl Content for InscriptionHtml {
Expand All @@ -23,10 +24,15 @@ mod tests {
txid: Txid::from_str("ec90757eb3b164aa43fc548faa2fa0c52025494f2c15d5ddf11260b4034ac6dc")
.unwrap(),
inscription: Inscription::Text("HELLOWORLD".into()),
satpoint: satpoint(1, 0),
}
.to_string(),
"
<h1>Inscription</h1>
<dl>
<dt>satpoint</dt>
<dd>1111111111111111111111111111111111111111111111111111111111111111:1:0</dd>
</dl>
HELLOWORLD
"
.unindent()
Expand All @@ -39,10 +45,15 @@ mod tests {
InscriptionHtml {
txid: Txid::from_str("ec90757eb3b164aa43fc548faa2fa0c52025494f2c15d5ddf11260b4034ac6dc").unwrap(),
inscription: Inscription::Png(vec![1; 100]),
satpoint: satpoint(1, 0),
}
.to_string(),
"
<h1>Inscription</h1>
<dl>
<dt>satpoint</dt>
<dd>1111111111111111111111111111111111111111111111111111111111111111:1:0</dd>
</dl>
<img src=\"data:image/png;base64,AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ==\">
"
.unindent()
Expand Down
4 changes: 4 additions & 0 deletions templates/inscription.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<h1>Inscription</h1>
<dl>
<dt>satpoint</dt>
<dd>{{ self.satpoint }}</dd>
</dl>
%% match &self.inscription {
%% Inscription::Text(content) => {
{{ content }}
Expand Down
Loading

0 comments on commit 61f6f54

Please sign in to comment.