Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ordering for reinscriptions and show all reinscriptions for sat #2279

Merged
merged 8 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 139 additions & 38 deletions src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -536,17 +536,30 @@ impl Index {
self.client.get_block(&hash).into_option()
}

pub(crate) fn get_inscription_id_by_sat(&self, sat: Sat) -> Result<Option<InscriptionId>> {
Ok(
self
.database
.begin_read()?
.open_multimap_table(SAT_TO_INSCRIPTION_ID)?
.get(&sat.n())?
.next()
.and_then(|result| result.ok())
.map(|inscription_id| Entry::load(*inscription_id.value())),
)
pub(crate) fn get_inscription_ids_by_sat(&self, sat: Sat) -> Result<Vec<InscriptionId>> {
let rtx = &self.database.begin_read()?;

let mut ids = rtx
.open_multimap_table(SAT_TO_INSCRIPTION_ID)?
.get(&sat.n())?
.filter_map(|result| {
result
.ok()
.map(|inscription_id| InscriptionId::load(*inscription_id.value()))
})
.collect::<Vec<InscriptionId>>();

if ids.len() > 1 {
let re_id_to_seq_num = rtx.open_table(REINSCRIPTION_ID_TO_SEQUENCE_NUMBER)?;
ids.sort_by_key(
|inscription_id| match re_id_to_seq_num.get(&inscription_id.store()) {
Ok(Some(num)) => num.value() + 1,
veryordinally marked this conversation as resolved.
Show resolved Hide resolved
_ => 0,
},
);
}

Ok(ids)
}

pub(crate) fn get_inscription_id_by_inscription_number(
Expand Down Expand Up @@ -598,37 +611,30 @@ impl Index {
}))
}

pub(crate) fn get_inscriptions_on_output(
&self,
outpoint: OutPoint,
) -> Result<Vec<InscriptionId>> {
Ok(
Self::inscriptions_on_output(
&self
.database
.begin_read()?
.open_multimap_table(SATPOINT_TO_INSCRIPTION_ID)?,
outpoint,
)?
.map(|(_satpoint, inscription_id)| inscription_id)
.collect(),
)
}

#[cfg(test)]
pub(crate) fn get_inscriptions_on_output_ordered(
pub(crate) fn get_inscriptions_on_output_with_satpoints(
&self,
outpoint: OutPoint,
) -> Result<Vec<(SatPoint, InscriptionId)>> {
let rtx = &self.database.begin_read()?;

let sat_to_id = rtx.open_multimap_table(SATPOINT_TO_INSCRIPTION_ID)?;

let re_id_to_seq_num = rtx.open_table(REINSCRIPTION_ID_TO_SEQUENCE_NUMBER)?;

Self::inscriptions_on_output_ordered(&re_id_to_seq_num, &sat_to_id, outpoint)
}

pub(crate) fn get_inscriptions_on_output(
&self,
outpoint: OutPoint,
) -> Result<Vec<InscriptionId>> {
Ok(
self
.get_inscriptions_on_output_with_satpoints(outpoint)?
.iter()
.map(|(_satpoint, inscription_id)| *inscription_id)
.collect(),
)
}

pub(crate) fn get_transaction(&self, txid: Txid) -> Result<Option<Transaction>> {
if txid == self.genesis_block_coinbase_txid {
Ok(Some(self.genesis_block_coinbase_transaction.clone()))
Expand Down Expand Up @@ -778,7 +784,7 @@ impl Index {
let id = id_result?;
result.insert(Entry::load(*satpoint.value()), Entry::load(*id.value()));
}
if result.len() == n.unwrap_or(usize::MAX) {
if result.len() >= n.unwrap_or(usize::MAX) {
break;
}
}
Expand Down Expand Up @@ -946,7 +952,7 @@ impl Index {
}
}

fn inscriptions_on_output<'a: 'tx, 'tx>(
fn inscriptions_on_output_unordered<'a: 'tx, 'tx>(
satpoint_to_id: &'a impl ReadableMultimapTable<&'static SatPointValue, &'static InscriptionIdValue>,
outpoint: OutPoint,
) -> Result<impl Iterator<Item = (SatPoint, InscriptionId)> + 'tx> {
Expand Down Expand Up @@ -980,7 +986,7 @@ impl Index {
satpoint_to_id: &'a impl ReadableMultimapTable<&'static SatPointValue, &'static InscriptionIdValue>,
outpoint: OutPoint,
) -> Result<Vec<(SatPoint, InscriptionId)>> {
let mut result = Self::inscriptions_on_output(satpoint_to_id, outpoint)?
let mut result = Self::inscriptions_on_output_unordered(satpoint_to_id, outpoint)?
.collect::<Vec<(SatPoint, InscriptionId)>>();

if result.len() <= 1 {
Expand All @@ -989,7 +995,7 @@ impl Index {

result.sort_by_key(|(_satpoint, inscription_id)| {
match re_id_to_seq_num.get(&inscription_id.store()) {
Ok(Some(num)) => num.value(),
Ok(Some(num)) => num.value() + 1,
veryordinally marked this conversation as resolved.
Show resolved Hide resolved
Ok(None) => 0,
_ => 0,
}
Expand Down Expand Up @@ -3003,7 +3009,7 @@ mod tests {
],
context
.index
.get_inscriptions_on_output_ordered(OutPoint { txid, vout: 0 })
.get_inscriptions_on_output_with_satpoints(OutPoint { txid, vout: 0 })
.unwrap()
.iter()
.map(|(_satpoint, inscription_id)| *inscription_id)
Expand Down Expand Up @@ -3055,7 +3061,102 @@ mod tests {
vec![(location, first), (location, second), (location, third)],
context
.index
.get_inscriptions_on_output_ordered(OutPoint { txid, vout: 0 })
.get_inscriptions_on_output_with_satpoints(OutPoint { txid, vout: 0 })
.unwrap()
)
}
}

#[test]
fn reinscriptions_sequence_numbers_are_tracked_correctly() {
for context in Context::configurations() {
context.mine_blocks(1);

let mut inscription_ids = vec![];

for i in 1..=5 {
let inscription_text =
inscription("text/plain;charset=utf-8", &format!("hello {}", i)).to_witness();
let txid = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(i, if i == 1 { 0 } else { 1 }, 0)], // for the first inscription use coinbase, otherwise use the previous tx
witness: inscription_text,
..Default::default()
});

let inscription_id = InscriptionId { txid, index: 0 };
inscription_ids.push(inscription_id);

context.mine_blocks(1);
}

let rtx = context.index.database.begin_read().unwrap();
let re_id_to_seq_num = rtx.open_table(REINSCRIPTION_ID_TO_SEQUENCE_NUMBER).unwrap();

for (index, id) in inscription_ids.iter().enumerate() {
match re_id_to_seq_num.get(&id.store()) {
Ok(Some(access_guard)) => {
let sequence_number = access_guard.value() + 1;
assert_eq!(
index as u64, sequence_number,
"sequence number mismatch for {:?}",
id
);
}
Ok(None) => assert_eq!(
index, 0,
"only first inscription should not have sequence number for {:?}",
id
),
Err(error) => panic!("Error retrieving sequence number: {}", error),
}
}
}
}

#[test]
fn reinscriptions_are_ordered_correctly_for_many_outpoints() {
for context in Context::configurations() {
context.mine_blocks(1);

let mut inscription_ids = vec![];

for i in 1..=21 {
let inscription_text =
inscription("text/plain;charset=utf-8", &format!("hello {}", i)).to_witness();
let txid = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(i, if i == 1 { 0 } else { 1 }, 0)], // for the first inscription use coinbase, otherwise use the previous tx
witness: inscription_text,
..Default::default()
});

let inscription_id = InscriptionId { txid, index: 0 };
inscription_ids.push(inscription_id);

context.mine_blocks(1);
}

let final_txid = inscription_ids.last().unwrap().txid;
let location = SatPoint {
outpoint: OutPoint {
txid: final_txid,
vout: 0,
},
offset: 0,
};

let expected_result = inscription_ids
.iter()
.map(|id| (location, *id))
.collect::<Vec<_>>();

assert_eq!(
expected_result,
context
.index
.get_inscriptions_on_output_with_satpoints(OutPoint {
txid: final_txid,
vout: 0
})
.unwrap()
)
}
Expand Down
8 changes: 1 addition & 7 deletions src/index/updater/inscription_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> {
} else if inscription.tx_in_offset != 0 {
Some(Curse::NotAtOffsetZero)
} else if inscribed_offsets.contains_key(&offset) {
let seq_num = self
.reinscription_id_to_seq_num
.iter()?
.next_back()
.and_then(|result| result.ok())
.map(|(_id, number)| number.value() + 1)
.unwrap_or(0);
let seq_num = self.reinscription_id_to_seq_num.len()?;

let sat = Self::calculate_sat(input_sat_ranges, offset);
log::info!("processing reinscription {inscription_id} on sat {:?}: sequence number {seq_num}, inscribed offsets {:?}", sat, inscribed_offsets);
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ impl Server {
sat,
satpoint,
blocktime: index.block_time(sat.height())?,
inscription: index.get_inscription_id_by_sat(sat)?,
inscriptions: index.get_inscription_ids_by_sat(sat)?,
}
.page(page_config, index.has_sat_index()?),
)
Expand Down
46 changes: 38 additions & 8 deletions src/templates/sat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub(crate) struct SatHtml {
pub(crate) sat: Sat,
pub(crate) satpoint: Option<SatPoint>,
pub(crate) blocktime: Blocktime,
pub(crate) inscription: Option<InscriptionId>,
pub(crate) inscriptions: Vec<InscriptionId>,
}

impl PageContent for SatHtml {
Expand All @@ -25,7 +25,7 @@ mod tests {
sat: Sat(0),
satpoint: None,
blocktime: Blocktime::confirmed(0),
inscription: None,
inscriptions: Vec::new(),
},
"
<h1>Sat 0</h1>
Expand Down Expand Up @@ -58,7 +58,7 @@ mod tests {
sat: Sat(2099999997689999),
satpoint: None,
blocktime: Blocktime::confirmed(0),
inscription: None,
inscriptions: Vec::new(),
},
"
<h1>Sat 2099999997689999</h1>
Expand Down Expand Up @@ -91,7 +91,7 @@ mod tests {
sat: Sat(1),
satpoint: None,
blocktime: Blocktime::confirmed(0),
inscription: None,
inscriptions: Vec::new(),
},
r"<h1>Sat 1</h1>.*<a class=prev href=/sat/0>prev</a>\n<a class=next href=/sat/2>next</a>.*",
);
Expand All @@ -104,9 +104,39 @@ mod tests {
sat: Sat(0),
satpoint: None,
blocktime: Blocktime::confirmed(0),
inscription: Some(inscription_id(1)),
inscriptions: vec![inscription_id(1)],
},
r"<h1>Sat 0</h1>.*<dt>inscription</dt><dd class=thumbnails><a href=/inscription/1{64}i1>.*</a></dd>.*",
"
<h1>Sat 0</h1>
.*
<dt>inscriptions</dt>
<dd class=thumbnails>
<a href=/inscription/1{64}i1>.*</a>
</dd>
.*"
.unindent(),
);
}

#[test]
fn sat_with_reinscription() {
assert_regex_match!(
SatHtml {
sat: Sat(0),
satpoint: None,
blocktime: Blocktime::confirmed(0),
inscriptions: vec![inscription_id(1), inscription_id(2)],
},
"
<h1>Sat 0</h1>
.*
<dt>inscriptions</dt>
<dd class=thumbnails>
<a href=/inscription/1{64}i1>.*</a>
<a href=/inscription/2{64}i2>.*</a>
</dd>
.*"
.unindent(),
);
}

Expand All @@ -117,7 +147,7 @@ mod tests {
sat: Sat::LAST,
satpoint: None,
blocktime: Blocktime::confirmed(0),
inscription: None,
inscriptions: Vec::new(),
},
r"<h1>Sat 2099999997689999</h1>.*<a class=prev href=/sat/2099999997689998>prev</a>\nnext.*",
);
Expand All @@ -130,7 +160,7 @@ mod tests {
sat: Sat(0),
satpoint: Some(satpoint(1, 0)),
blocktime: Blocktime::confirmed(0),
inscription: None,
inscriptions: Vec::new(),
},
"<h1>Sat 0</h1>.*<dt>location</dt><dd class=monospace>1{64}:1:0</dd>.*",
);
Expand Down
9 changes: 7 additions & 2 deletions templates/sat.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ <h1>Sat {{ self.sat.n() }}</h1>
<dt>offset</dt><dd>{{ self.sat.third() }}</dd>
<dt>rarity</dt><dd><span class={{self.sat.rarity()}}>{{ self.sat.rarity() }}</span></dd>
<dt>timestamp</dt><dd><time>{{self.blocktime.timestamp()}}</time>{{self.blocktime.suffix()}}</dd>
%% if let Some((inscription)) = &self.inscription {
<dt>inscription</dt><dd class=thumbnails>{{ Iframe::thumbnail(*inscription) }}</dd>
%% if !self.inscriptions.is_empty() {
veryordinally marked this conversation as resolved.
Show resolved Hide resolved
<dt>inscriptions</dt>
<dd class=thumbnails>
%% for inscription in &self.inscriptions {
{{Iframe::thumbnail(*inscription)}}
%% }
</dd>
%% }
%% if let Some(satpoint) = self.satpoint {
<dt>location</dt><dd class=monospace>{{ satpoint }}</dd>
Expand Down