From 5d3cbbb4e6b7507d0eb938c5752800482d7acfb1 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 13:05:14 -0800 Subject: [PATCH 01/49] Allow storing arrays of parents. --- src/index.rs | 21 ++++++++++++--------- src/index/entry.rs | 10 +++++----- src/index/updater/inscription_updater.rs | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/index.rs b/src/index.rs index 7f887b79d0..8ce5791295 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1081,14 +1081,17 @@ impl Index { .open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY) .unwrap(); - let parent_sequence_number = InscriptionEntry::load( + let parents_sequences = InscriptionEntry::load( sequence_number_to_inscription_entry .get(sequence_number) .unwrap() .unwrap() .value(), ) - .parent + .parents; + + let parent_sequence_number = parents_sequences + .first() .unwrap(); let entry = InscriptionEntry::load( @@ -1814,7 +1817,7 @@ impl Index { None }; - let parent = match entry.parent { + let parent = match entry.parents.first() { Some(parent) => Some( InscriptionEntry::load( sequence_number_to_inscription_entry @@ -4389,8 +4392,8 @@ mod tests { .get_inscription_entry(inscription_id) .unwrap() .unwrap() - .parent - .is_none()); + .parents + .is_empty()); } } @@ -4436,8 +4439,8 @@ mod tests { .get_inscription_entry(inscription_id) .unwrap() .unwrap() - .parent - .is_none()); + .parents + .is_empty()); } } @@ -4651,8 +4654,8 @@ mod tests { .get_inscription_entry(inscription_id) .unwrap() .unwrap() - .parent - .is_none()); + .parents + .is_empty()); } } diff --git a/src/index/entry.rs b/src/index/entry.rs index 91923082fe..bc7bb31cbb 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -199,7 +199,7 @@ pub(crate) struct InscriptionEntry { pub(crate) height: u32, pub(crate) id: InscriptionId, pub(crate) inscription_number: i32, - pub(crate) parent: Option, + pub(crate) parents: Vec, pub(crate) sat: Option, pub(crate) sequence_number: u32, pub(crate) timestamp: u32, @@ -211,7 +211,7 @@ pub(crate) type InscriptionEntryValue = ( u32, // height InscriptionIdValue, // inscription id i32, // inscription number - Option, // parent + Vec, // parent Option, // sat u32, // sequence number u32, // timestamp @@ -228,7 +228,7 @@ impl Entry for InscriptionEntry { height, id, inscription_number, - parent, + parents, sat, sequence_number, timestamp, @@ -240,7 +240,7 @@ impl Entry for InscriptionEntry { height, id: InscriptionId::load(id), inscription_number, - parent, + parents, sat: sat.map(Sat), sequence_number, timestamp, @@ -254,7 +254,7 @@ impl Entry for InscriptionEntry { self.height, self.id.store(), self.inscription_number, - self.parent, + self.parents, self.sat.map(Sat::n), self.sequence_number, self.timestamp, diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index ee10151f1e..8f8bb33417 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -531,7 +531,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { height: self.height, id: inscription_id, inscription_number, - parent: parent_sequence_number, + parents: parent_sequence_number.map_or(vec![], |p| vec![p]), sat, sequence_number, timestamp: self.timestamp, From 536fa77a526d33270415cbe3cad989634328f4b2 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 13:09:44 -0800 Subject: [PATCH 02/49] Update schema version. --- src/index.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.rs b/src/index.rs index 8ce5791295..b67dd0e501 100644 --- a/src/index.rs +++ b/src/index.rs @@ -43,7 +43,7 @@ mod updater; #[cfg(test)] pub(crate) mod testing; -const SCHEMA_VERSION: u64 = 18; +const SCHEMA_VERSION: u64 = 19; macro_rules! define_table { ($name:ident, $key:ty, $value:ty) => { From a89cd8c73652f561ddc8bab22f4248a49e135c73 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 14:44:29 -0800 Subject: [PATCH 03/49] Parse arrays of parents. --- src/index.rs | 26 +++++------ src/inscriptions/envelope.rs | 5 +- src/inscriptions/inscription.rs | 52 +++++++++++++-------- src/inscriptions/tag.rs | 77 +++++++++++++++++++++---------- src/subcommand/server.rs | 18 ++++---- src/subcommand/wallet/inscribe.rs | 32 ++++++------- src/test.rs | 4 +- 7 files changed, 125 insertions(+), 89 deletions(-) diff --git a/src/index.rs b/src/index.rs index b67dd0e501..aca5f7367e 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1090,9 +1090,7 @@ impl Index { ) .parents; - let parent_sequence_number = parents_sequences - .first() - .unwrap(); + let parent_sequence_number = parents_sequences.first().unwrap(); let entry = InscriptionEntry::load( sequence_number_to_inscription_entry @@ -4422,7 +4420,7 @@ mod tests { Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], ..Default::default() } .to_witness(), @@ -4469,7 +4467,7 @@ mod tests { Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], ..Default::default() } .to_witness(), @@ -4523,7 +4521,7 @@ mod tests { Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], ..Default::default() } .to_witness(), @@ -4577,7 +4575,7 @@ mod tests { Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], ..Default::default() } .to_witness(), @@ -4631,13 +4629,11 @@ mod tests { Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some( - parent_inscription_id - .value() - .into_iter() - .chain(iter::once(0)) - .collect(), - ), + parents: vec![parent_inscription_id + .value() + .into_iter() + .chain(iter::once(0)) + .collect()], ..Default::default() } .to_witness(), @@ -4810,7 +4806,7 @@ mod tests { let child_inscription = Inscription { content_type: Some("text/plain".into()), body: Some("pointer-child".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], pointer: Some(0u64.to_le_bytes().to_vec()), ..Default::default() }; diff --git a/src/inscriptions/envelope.rs b/src/inscriptions/envelope.rs index 0ef863c831..876173047e 100644 --- a/src/inscriptions/envelope.rs +++ b/src/inscriptions/envelope.rs @@ -53,7 +53,8 @@ impl From for ParsedEnvelope { let delegate = Tag::Delegate.remove_field(&mut fields); let metadata = Tag::Metadata.remove_field(&mut fields); let metaprotocol = Tag::Metaprotocol.remove_field(&mut fields); - let parent = Tag::Parent.remove_field(&mut fields); + // let parent = Tag::Parent.remove_field(&mut fields); + let parents = Tag::Parent.remove_array_field(&mut fields); let pointer = Tag::Pointer.remove_field(&mut fields); let unrecognized_even_field = fields @@ -76,7 +77,7 @@ impl From for ParsedEnvelope { incomplete_field, metadata, metaprotocol, - parent, + parents, pointer, unrecognized_even_field, }, diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index 057f34f76e..c9e4e883a8 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -21,7 +21,7 @@ pub struct Inscription { pub incomplete_field: bool, pub metadata: Option>, pub metaprotocol: Option>, - pub parent: Option>, + pub parents: Vec>, pub pointer: Option>, pub unrecognized_even_field: bool, } @@ -102,7 +102,7 @@ impl Inscription { delegate: delegate.map(|delegate| delegate.value()), metadata, metaprotocol: metaprotocol.map(|metaprotocol| metaprotocol.into_bytes()), - parent: parent.map(|parent| parent.value()), + parents: parent.map_or(vec![], |parent| vec![parent.value()]), pointer: pointer.map(Self::pointer_value), ..Default::default() }) @@ -130,7 +130,7 @@ impl Inscription { Tag::ContentType.encode(&mut builder, &self.content_type); Tag::ContentEncoding.encode(&mut builder, &self.content_encoding); Tag::Metaprotocol.encode(&mut builder, &self.metaprotocol); - Tag::Parent.encode(&mut builder, &self.parent); + Tag::Parent.encode(&mut builder, &self.parents.first().cloned()); Tag::Delegate.encode(&mut builder, &self.delegate); Tag::Pointer.encode(&mut builder, &self.pointer); Tag::Metadata.encode(&mut builder, &self.metadata); @@ -248,7 +248,19 @@ impl Inscription { } pub(crate) fn parent(&self) -> Option { - Self::inscription_id_field(&self.parent) + Self::inscription_id_field(&self.parents.first().cloned()) + } + + pub(crate) fn parents(&self) -> Vec { + self + .parents + .iter() + .map(|p| { + // the option detour is a bit awkward + Self::inscription_id_field(&Some(p.clone())) + }) + .flatten() + .collect() } pub(crate) fn pointer(&self) -> Option { @@ -421,7 +433,7 @@ mod tests { #[test] fn inscription_with_no_parent_field_has_no_parent() { assert!(Inscription { - parent: None, + parents: vec![], ..Default::default() } .parent() @@ -431,7 +443,7 @@ mod tests { #[test] fn inscription_with_parent_field_shorter_than_txid_length_has_no_parent() { assert!(Inscription { - parent: Some(vec![]), + parents: vec![vec![]], ..Default::default() } .parent() @@ -441,7 +453,7 @@ mod tests { #[test] fn inscription_with_parent_field_longer_than_txid_and_index_has_no_parent() { assert!(Inscription { - parent: Some(vec![1; 37]), + parents: vec![vec![1; 37]], ..Default::default() } .parent() @@ -455,7 +467,7 @@ mod tests { parent[35] = 0; assert!(Inscription { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .parent() @@ -469,7 +481,7 @@ mod tests { parent[34] = 0; assert!(Inscription { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .parent() @@ -500,11 +512,11 @@ mod tests { fn inscription_parent_txid_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![ + parents: vec![vec![ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - ]), + ]], ..Default::default() } .parent() @@ -520,7 +532,7 @@ mod tests { fn inscription_parent_with_zero_byte_index_field_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![1; 32]), + parents: vec![vec![1; 32]], ..Default::default() } .parent() @@ -534,11 +546,11 @@ mod tests { fn inscription_parent_with_one_byte_index_field_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![ + parents: vec![vec![ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01 - ]), + ]], ..Default::default() } .parent() @@ -552,11 +564,11 @@ mod tests { fn inscription_parent_with_two_byte_index_field_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![ + parents: vec![vec![ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x02 - ]), + ]], ..Default::default() } .parent() @@ -570,11 +582,11 @@ mod tests { fn inscription_parent_with_three_byte_index_field_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![ + parents: vec![vec![ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x02, 0x03 - ]), + ]], ..Default::default() } .parent() @@ -588,11 +600,11 @@ mod tests { fn inscription_parent_with_four_byte_index_field_is_deserialized_correctly() { assert_eq!( Inscription { - parent: Some(vec![ + parents: vec![vec![ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x02, 0x03, 0x04, - ]), + ]], ..Default::default() } .parent() diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index f6b95e4a48..3950159069 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -16,9 +16,19 @@ pub(crate) enum Tag { Nop, } +enum TagParsingStrategy { + First, + Chunked, + Array, +} + impl Tag { - fn is_chunked(self) -> bool { - matches!(self, Self::Metadata) + fn parsing_strategy(self) -> TagParsingStrategy { + match self { + Tag::Metadata => TagParsingStrategy::Chunked, + Tag::Parent => TagParsingStrategy::Array, + _ => TagParsingStrategy::First, + } } pub(crate) fn bytes(self) -> &'static [u8] { @@ -41,16 +51,21 @@ impl Tag { let mut tmp = script::Builder::new(); mem::swap(&mut tmp, builder); - if self.is_chunked() { - for chunk in value.chunks(MAX_SCRIPT_ELEMENT_SIZE) { + match self.parsing_strategy() { + TagParsingStrategy::First | TagParsingStrategy::Array => { tmp = tmp .push_slice::<&script::PushBytes>(self.bytes().try_into().unwrap()) - .push_slice::<&script::PushBytes>(chunk.try_into().unwrap()); + .push_slice::<&script::PushBytes>(value.as_slice().try_into().unwrap()); } - } else { - tmp = tmp - .push_slice::<&script::PushBytes>(self.bytes().try_into().unwrap()) - .push_slice::<&script::PushBytes>(value.as_slice().try_into().unwrap()); + TagParsingStrategy::Chunked => { + for chunk in value.chunks(MAX_SCRIPT_ELEMENT_SIZE) { + tmp = tmp + .push_slice::<&script::PushBytes>(self.bytes().try_into().unwrap()) + .push_slice::<&script::PushBytes>(chunk.try_into().unwrap()); + } + } // TagParsingStrategy::Array => { + // unimplemented!() + // } } mem::swap(&mut tmp, builder); @@ -58,28 +73,40 @@ impl Tag { } pub(crate) fn remove_field(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Option> { - if self.is_chunked() { - let value = fields.remove(self.bytes())?; + match self.parsing_strategy() { + TagParsingStrategy::First => { + let values = fields.get_mut(self.bytes())?; - if value.is_empty() { - None - } else { - Some(value.into_iter().flatten().cloned().collect()) - } - } else { - let values = fields.get_mut(self.bytes())?; + if values.is_empty() { + None + } else { + let value = values.remove(0).to_vec(); - if values.is_empty() { - None - } else { - let value = values.remove(0).to_vec(); + if values.is_empty() { + fields.remove(self.bytes()); + } - if values.is_empty() { - fields.remove(self.bytes()); + Some(value) } + } + TagParsingStrategy::Chunked => { + let value = fields.remove(self.bytes())?; - Some(value) + if value.is_empty() { + None + } else { + Some(value.into_iter().flatten().cloned().collect()) + } + } + TagParsingStrategy::Array => { + panic!("Array-type fields must not be removed as a simple byte array.") } } } + + pub(crate) fn remove_array_field(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Vec> { + let values = fields.remove(self.bytes()).unwrap_or(vec![]); + // .cloned() doesn't work for Vec<&[u8]> + values.into_iter().map(|v| v.to_vec()).collect() + } } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index ce8eaebea5..fce5038eff 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -4408,7 +4408,7 @@ mod tests { Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_id.value()), + parents: vec![parent_id.value()], ..Default::default() } .to_witness(), @@ -4526,7 +4526,7 @@ next Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], ..Default::default() } .to_witness(), @@ -4598,7 +4598,7 @@ next Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], ..Default::default() } .to_witness(), @@ -4645,7 +4645,7 @@ next Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], ..Default::default() } .to_witness(), @@ -4657,7 +4657,7 @@ next Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], ..Default::default() } .to_witness(), @@ -4669,7 +4669,7 @@ next Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], ..Default::default() } .to_witness(), @@ -4681,7 +4681,7 @@ next Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], ..Default::default() } .to_witness(), @@ -4693,7 +4693,7 @@ next Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], ..Default::default() } .to_witness(), @@ -5364,7 +5364,7 @@ next builder = Inscription { content_type: Some("text/plain".into()), body: Some("hello".into()), - parent: Some(parent_inscription_id.value()), + parents: vec![parent_inscription_id.value()], unrecognized_even_field: false, ..Default::default() } diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 6940603cb8..19482451d0 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -478,7 +478,7 @@ mod tests { inscriptions.insert(parent_info.location, vec![parent_inscription]); let child_inscription = InscriptionTemplate { - parent: Some(parent_inscription), + parents: vec![parent_inscription], ..Default::default() } .into(); @@ -820,17 +820,17 @@ inscriptions: let inscriptions = vec![ InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), @@ -928,17 +928,17 @@ inscriptions: let inscriptions = vec![ InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], pointer: Some(10_000), } .into(), InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], pointer: Some(11_111), } .into(), InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], pointer: Some(13_3333), } .into(), @@ -1044,17 +1044,17 @@ inscriptions: let inscriptions = vec![ InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), @@ -1120,17 +1120,17 @@ inscriptions: let inscriptions = vec![ InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), @@ -1290,17 +1290,17 @@ inscriptions: let inscriptions = vec![ InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), InscriptionTemplate { - parent: Some(parent), + parents: vec![parent], ..Default::default() } .into(), diff --git a/src/test.rs b/src/test.rs index 5e90c2c453..98a96e80f4 100644 --- a/src/test.rs +++ b/src/test.rs @@ -103,14 +103,14 @@ pub(crate) fn tx_out(value: u64, address: Address) -> TxOut { #[derive(Default, Debug)] pub(crate) struct InscriptionTemplate { - pub(crate) parent: Option, + pub(crate) parents: Vec, pub(crate) pointer: Option, } impl From for Inscription { fn from(template: InscriptionTemplate) -> Self { Self { - parent: template.parent.map(|id| id.value()), + parents: template.parents.into_iter().map(|id| id.value()).collect(), pointer: template.pointer.map(Inscription::pointer_value), ..Default::default() } From 12c6117d26713c83bfede7f5041f984d12e81c0b Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 6 Mar 2024 15:27:48 -0800 Subject: [PATCH 04/49] Update explorer --- src/api.rs | 2 +- src/index.rs | 14 +++++++------- src/subcommand/server.rs | 8 ++++---- src/templates/inscription.rs | 6 +++--- templates/inscription.html | 6 ++++-- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/api.rs b/src/api.rs index e1db4f0b4a..f89bd29993 100644 --- a/src/api.rs +++ b/src/api.rs @@ -73,7 +73,7 @@ pub struct Inscription { pub inscription_number: i32, pub next: Option, pub output_value: Option, - pub parent: Option, + pub parents: Vec, pub previous: Option, pub rune: Option, pub sat: Option, diff --git a/src/index.rs b/src/index.rs index aca5f7367e..3c9b1caf03 100644 --- a/src/index.rs +++ b/src/index.rs @@ -168,7 +168,7 @@ pub(crate) struct TransactionInfo { pub(crate) struct InscriptionInfo { pub(crate) children: Vec, pub(crate) entry: InscriptionEntry, - pub(crate) parent: Option, + pub(crate) parents: Vec, pub(crate) output: Option, pub(crate) satpoint: SatPoint, pub(crate) inscription: Inscription, @@ -1815,8 +1815,9 @@ impl Index { None }; - let parent = match entry.parents.first() { - Some(parent) => Some( + let mut parents = Vec::new(); + for parent in entry.parents.iter() { + parents.push( InscriptionEntry::load( sequence_number_to_inscription_entry .get(parent)? @@ -1824,9 +1825,8 @@ impl Index { .value(), ) .id, - ), - None => None, - }; + ); + } let mut charms = entry.charms; @@ -1837,7 +1837,7 @@ impl Index { Ok(Some(InscriptionInfo { children, entry, - parent, + parents, output, satpoint, inscription, diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index fce5038eff..525f91e319 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1464,7 +1464,7 @@ impl Server { children: info.children, inscription_number: info.entry.inscription_number, genesis_height: info.entry.height, - parent: info.parent, + parents: info.parents, genesis_fee: info.entry.fee, output_value: info.output.as_ref().map(|o| o.value), address: info @@ -1499,7 +1499,7 @@ impl Server { inscription_number: info.entry.inscription_number, next: info.next, output: info.output, - parent: info.parent, + parents: info.parents, previous: info.previous, rune: info.rune, sat: info.entry.sat, @@ -4554,8 +4554,8 @@ next assert_eq!( server .get_json::(format!("/inscription/{inscription_id}")) - .parent, - Some(parent_inscription_id), + .parents, + vec![parent_inscription_id], ); assert_eq!( diff --git a/src/templates/inscription.rs b/src/templates/inscription.rs index 9f3ae8a947..63d69c6c3d 100644 --- a/src/templates/inscription.rs +++ b/src/templates/inscription.rs @@ -11,7 +11,7 @@ pub(crate) struct InscriptionHtml { pub(crate) inscription_number: i32, pub(crate) next: Option, pub(crate) output: Option, - pub(crate) parent: Option, + pub(crate) parents: Vec, pub(crate) previous: Option, pub(crate) rune: Option, pub(crate) sat: Option, @@ -209,7 +209,7 @@ mod tests { fn with_parent() { assert_regex_match!( InscriptionHtml { - parent: Some(inscription_id(2)), + parents: vec![inscription_id(2)], genesis_fee: 1, inscription: inscription("text/plain;charset=utf-8", "HELLOWORLD"), inscription_id: inscription_id(1), @@ -227,7 +227,7 @@ mod tests {
id
1{64}i1
-
parent
+
parents
diff --git a/templates/inscription.html b/templates/inscription.html index 373f11ccf2..5128329e88 100644 --- a/templates/inscription.html +++ b/templates/inscription.html @@ -28,8 +28,10 @@

Inscription {{ self.inscription_number }}

%% }
id
{{ self.inscription_id }}
-%% if let Some(parent) = &self.parent { -
parent
+%% if !&self.parents.is_empty() { +
parents
+%% } +%% for parent in &self.parents {
{{Iframe::thumbnail(*parent)}} From 595ed9cff78a15d0fc8e53b5a946ad064154e86b Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 15:22:59 -0800 Subject: [PATCH 05/49] Remove parent() method from Inscription struct. --- src/index/updater/inscription_updater.rs | 2 +- src/inscriptions/inscription.rs | 44 +++++++++++++----------- src/subcommand/decode.rs | 2 +- src/subcommand/wallet/inscribe.rs | 8 +++-- src/wallet/inscribe/batch.rs | 10 +++--- 5 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 8f8bb33417..fd72902694 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -215,7 +215,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { cursed: curse.is_some() && !jubilant, fee: 0, hidden: inscription.payload.hidden(), - parent: inscription.payload.parent(), + parent: inscription.payload.parents().first().map(|p| p.clone()), pointer: inscription.payload.pointer(), reinscription: inscribed_offsets.get(&offset).is_some(), unbound: current_input_value == 0 diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index c9e4e883a8..2de987588a 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -247,10 +247,6 @@ impl Inscription { str::from_utf8(self.metaprotocol.as_ref()?).ok() } - pub(crate) fn parent(&self) -> Option { - Self::inscription_id_field(&self.parents.first().cloned()) - } - pub(crate) fn parents(&self) -> Vec { self .parents @@ -436,8 +432,8 @@ mod tests { parents: vec![], ..Default::default() } - .parent() - .is_none()); + .parents() + .is_empty()); } #[test] @@ -446,8 +442,8 @@ mod tests { parents: vec![vec![]], ..Default::default() } - .parent() - .is_none()); + .parents() + .is_empty()); } #[test] @@ -456,8 +452,8 @@ mod tests { parents: vec![vec![1; 37]], ..Default::default() } - .parent() - .is_none()); + .parents() + .is_empty()); } #[test] @@ -466,12 +462,12 @@ mod tests { parent[35] = 0; - assert!(Inscription { + assert!(!Inscription { parents: vec![parent], ..Default::default() } - .parent() - .is_some()); + .parents() + .is_empty()); } #[test] @@ -484,8 +480,8 @@ mod tests { parents: vec![parent], ..Default::default() } - .parent() - .is_none()); + .parents() + .is_empty()); } #[test] @@ -519,7 +515,8 @@ mod tests { ]], ..Default::default() } - .parent() + .parents() + .first() .unwrap() .txid, "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100" @@ -535,7 +532,8 @@ mod tests { parents: vec![vec![1; 32]], ..Default::default() } - .parent() + .parents() + .first() .unwrap() .index, 0 @@ -553,7 +551,8 @@ mod tests { ]], ..Default::default() } - .parent() + .parents() + .first() .unwrap() .index, 1 @@ -571,7 +570,8 @@ mod tests { ]], ..Default::default() } - .parent() + .parents() + .first() .unwrap() .index, 0x0201, @@ -589,7 +589,8 @@ mod tests { ]], ..Default::default() } - .parent() + .parents() + .first() .unwrap() .index, 0x030201, @@ -607,7 +608,8 @@ mod tests { ]], ..Default::default() } - .parent() + .parents() + .first() .unwrap() .index, 0x04030201, diff --git a/src/subcommand/decode.rs b/src/subcommand/decode.rs index f0562a126a..2fd3fb184d 100644 --- a/src/subcommand/decode.rs +++ b/src/subcommand/decode.rs @@ -45,7 +45,7 @@ impl TryFrom for CompactInscription { .transpose()?, content_type: inscription.content_type().map(str::to_string), metaprotocol: inscription.metaprotocol().map(str::to_string), - parent: inscription.parent(), + parent: inscription.parents().first().map(|p| p.clone()), pointer: inscription.pointer(), body: inscription.body.map(hex::encode), duplicate_field: inscription.duplicate_field, diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 19482451d0..1ecb64ae53 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -1337,15 +1337,19 @@ inscriptions: parent, ParsedEnvelope::from_transaction(&reveal_tx)[0] .payload - .parent() + .parents() + .first() .unwrap() + .clone() ); assert_eq!( parent, ParsedEnvelope::from_transaction(&reveal_tx)[1] .payload - .parent() + .parents() + .first() .unwrap() + .clone() ); let sig_vbytes = 17; diff --git a/src/wallet/inscribe/batch.rs b/src/wallet/inscribe/batch.rs index 1c27df1669..43cdf3d512 100644 --- a/src/wallet/inscribe/batch.rs +++ b/src/wallet/inscribe/batch.rs @@ -220,10 +220,12 @@ impl Batch { change: [Address; 2], ) -> Result<(Transaction, Transaction, TweakedKeyPair, u64)> { if let Some(parent_info) = &self.parent_info { - assert!(self - .inscriptions - .iter() - .all(|inscription| inscription.parent().unwrap() == parent_info.id)) + assert!(self.inscriptions.iter().all(|inscription| inscription + .parents() + .first() + .unwrap() + .clone() + == parent_info.id)) } match self.mode { From 5c1a753083527338043afd40e7e8a05962e9b96f Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 6 Mar 2024 15:40:43 -0800 Subject: [PATCH 06/49] Fix something --- tests/json_api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/json_api.rs b/tests/json_api.rs index 77bd07ec99..c32fe08ef5 100644 --- a/tests/json_api.rs +++ b/tests/json_api.rs @@ -163,7 +163,7 @@ fn get_inscription() { inscription_number: 0, next: None, output_value: Some(10000), - parent: None, + parents: Vec::new(), previous: None, rune: None, sat: Some(Sat(50 * COIN_VALUE)), From ec979fcde18b1ff79463382d26513bccd56c0859 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 15:40:30 -0800 Subject: [PATCH 07/49] Convert parent field to parents on various inscription structs. --- src/subcommand/decode.rs | 6 +++--- tests/decode.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/subcommand/decode.rs b/src/subcommand/decode.rs index 2fd3fb184d..cb8581e469 100644 --- a/src/subcommand/decode.rs +++ b/src/subcommand/decode.rs @@ -26,8 +26,8 @@ pub struct CompactInscription { pub metadata: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub metaprotocol: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub parent: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub parents: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] pub pointer: Option, #[serde(default, skip_serializing_if = "std::ops::Not::not")] @@ -45,7 +45,7 @@ impl TryFrom for CompactInscription { .transpose()?, content_type: inscription.content_type().map(str::to_string), metaprotocol: inscription.metaprotocol().map(str::to_string), - parent: inscription.parents().first().map(|p| p.clone()), + parents: inscription.parents(), pointer: inscription.pointer(), body: inscription.body.map(hex::encode), duplicate_field: inscription.duplicate_field, diff --git a/tests/decode.rs b/tests/decode.rs index d27d855d8b..e503db9067 100644 --- a/tests/decode.rs +++ b/tests/decode.rs @@ -136,7 +136,7 @@ fn compact() { incomplete_field: false, metadata: None, metaprotocol: None, - parent: None, + parents: vec![], pointer: None, unrecognized_even_field: false, }], From 65d703ccaeb4d3d8a704520fdb7cbfe38843eab7 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 15:42:51 -0800 Subject: [PATCH 08/49] Fix HTML serialization tests. --- src/subcommand/server.rs | 2 +- tests/wallet/inscribe.rs | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 525f91e319..e33ea55336 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -4543,7 +4543,7 @@ next server.assert_response_regex( format!("/inscription/{inscription_id}"), StatusCode::OK, - format!(".*Inscription 1.*
parent
.*
.**.*"), + format!(".*Inscription 1.*
parents
.*
.**.*"), ); server.assert_response_regex( format!("/inscription/{parent_inscription_id}"), diff --git a/tests/wallet/inscribe.rs b/tests/wallet/inscribe.rs index b543dd2e7e..5cb0595fd0 100644 --- a/tests/wallet/inscribe.rs +++ b/tests/wallet/inscribe.rs @@ -650,7 +650,7 @@ fn inscribe_with_parent_inscription_and_fee_rate() { ord_rpc_server.assert_response_regex( format!("/inscription/{}", child_output.inscriptions[0].id), format!( - ".*
parent
.*.*", + ".*
parents
.*
.*", child_output.parent.unwrap() ), ); @@ -1043,12 +1043,12 @@ fn batch_inscribe_with_multiple_inscriptions_with_parent() { ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), - r".*
parent
\s*
.*
.*", + r".*
parents
\s*
.*
.*", ); ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[1].id), - r".*
parent
\s*
.*
.*", + r".*
parents
\s*
.*
.*", ); let request = ord_rpc_server.request(format!("/content/{}", output.inscriptions[2].id)); @@ -1275,7 +1275,7 @@ fn batch_in_separate_outputs_with_parent() { ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
10000
.*.*
location
.*
{}:0
.*", + r".*
parents
\s*
.*{parent_id}.*
.*
output value
.*
10000
.*.*
location
.*
{}:0
.*", output_1 ), ); @@ -1283,7 +1283,7 @@ fn batch_in_separate_outputs_with_parent() { ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[1].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
10000
.*.*
location
.*
{}:0
.*", + r".*
parents
\s*
.*{parent_id}.*
.*
output value
.*
10000
.*.*
location
.*
{}:0
.*", output_2 ), ); @@ -1291,7 +1291,7 @@ fn batch_in_separate_outputs_with_parent() { ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[2].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
10000
.*.*
location
.*
{}:0
.*", + r".*
parents
\s*
.*{parent_id}.*
.*
output value
.*
10000
.*.*
location
.*
{}:0
.*", output_3 ), ); @@ -1353,7 +1353,7 @@ fn batch_in_separate_outputs_with_parent_and_non_default_postage() { ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[0].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
777
.*.*
location
.*
{}:0
.*", + r".*
parents
\s*
.*{parent_id}.*
.*
output value
.*
777
.*.*
location
.*
{}:0
.*", output_1 ), ); @@ -1361,7 +1361,7 @@ fn batch_in_separate_outputs_with_parent_and_non_default_postage() { ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[1].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
777
.*.*
location
.*
{}:0
.*", + r".*
parents
\s*
.*{parent_id}.*
.*
output value
.*
777
.*.*
location
.*
{}:0
.*", output_2 ), ); @@ -1369,7 +1369,7 @@ fn batch_in_separate_outputs_with_parent_and_non_default_postage() { ord_rpc_server.assert_response_regex( format!("/inscription/{}", output.inscriptions[2].id), format!( - r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
777
.*.*
location
.*
{}:0
.*", + r".*
parents
\s*
.*{parent_id}.*
.*
output value
.*
777
.*.*
location
.*
{}:0
.*", output_3 ), ); @@ -2400,7 +2400,7 @@ inscriptions: ord_rpc_server.assert_response_regex( format!("/inscription/{}", inscription_1.id), - format!(r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", + format!(r".*
parents
\s*
.*{parent_id}.*
.*
output value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", 50 * COIN_VALUE, sat_1, inscription_1.location, @@ -2409,7 +2409,7 @@ inscriptions: ord_rpc_server.assert_response_regex( format!("/inscription/{}", inscription_2.id), - format!(r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", + format!(r".*
parents
\s*
.*{parent_id}.*
.*
output value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", 50 * COIN_VALUE, sat_2, inscription_2.location @@ -2418,7 +2418,7 @@ inscriptions: ord_rpc_server.assert_response_regex( format!("/inscription/{}", inscription_3.id), - format!(r".*
parent
\s*
.*{parent_id}.*
.*
output value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", + format!(r".*
parents
\s*
.*{parent_id}.*
.*
output value
.*
{}
.*
sat
.*
.*{}.*
.*
location
.*
{}
.*", 50 * COIN_VALUE, sat_3, inscription_3.location From da3ffb246cc77aaef3df610d056159058d7b86be Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 16:54:33 -0800 Subject: [PATCH 09/49] Create unit test for two-parent-inscription. --- src/index.rs | 110 ++++++++++++++++++----- src/index/event.rs | 2 +- src/index/updater/inscription_updater.rs | 51 ++++++----- src/inscriptions/inscription.rs | 2 +- src/inscriptions/tag.rs | 13 +++ 5 files changed, 128 insertions(+), 50 deletions(-) diff --git a/src/index.rs b/src/index.rs index 3c9b1caf03..0d350d8812 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1063,10 +1063,10 @@ impl Index { } #[cfg(test)] - pub(crate) fn get_parent_by_inscription_id( + pub(crate) fn get_parents_by_inscription_id( &self, inscription_id: InscriptionId, - ) -> InscriptionId { + ) -> Vec { let rtx = self.database.begin_read().unwrap(); let sequence_number = rtx @@ -1090,17 +1090,19 @@ impl Index { ) .parents; - let parent_sequence_number = parents_sequences.first().unwrap(); - - let entry = InscriptionEntry::load( - sequence_number_to_inscription_entry - .get(parent_sequence_number) - .unwrap() - .unwrap() - .value(), - ); - - entry.id + parents_sequences + .into_iter() + .map(|parent_sequence_number| { + InscriptionEntry::load( + sequence_number_to_inscription_entry + .get(parent_sequence_number) + .unwrap() + .unwrap() + .value(), + ) + .id + }) + .collect() } pub(crate) fn get_children_by_sequence_number_paginated( @@ -4480,8 +4482,8 @@ mod tests { let inscription_id = InscriptionId { txid, index: 0 }; assert_eq!( - context.index.get_parent_by_inscription_id(inscription_id), - parent_inscription_id + context.index.get_parents_by_inscription_id(inscription_id), + vec![parent_inscription_id] ); assert_eq!( @@ -4494,6 +4496,70 @@ mod tests { } } + #[test] + fn inscription_with_two_parent_tags_and_parents_has_parent_entries() { + for context in Context::configurations() { + context.mine_blocks(2); + + let parent_txid_a = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..Default::default() + }); + let parent_txid_b = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, inscription("text/plain", "world").to_witness())], + ..Default::default() + }); + + context.mine_blocks(1); + + let parent_inscription_id_a = InscriptionId { + txid: parent_txid_a, + index: 0, + }; + let parent_inscription_id_b = InscriptionId { + txid: parent_txid_b, + index: 0, + }; + + let multi_parent_inscription = Inscription { + content_type: Some("text/plain".into()), + body: Some("hello".into()), + parents: vec![ + parent_inscription_id_a.value(), + parent_inscription_id_b.value(), + ], + ..Default::default() + }; + let multi_parent_witness = multi_parent_inscription.to_witness(); + + let revelation_input = (3, 1, 0, multi_parent_witness); + + let parent_b_input = (3, 2, 0, Witness::new()); + + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[revelation_input, parent_b_input], + ..Default::default() + }); + + context.mine_blocks(1); + + let inscription_id = InscriptionId { txid, index: 0 }; + + assert_eq!( + context.index.get_parents_by_inscription_id(inscription_id), + vec![parent_inscription_id_a, parent_inscription_id_b] + ); + + assert_eq!( + context + .index + .get_children_by_inscription_id(parent_inscription_id_a) + .unwrap(), + vec![inscription_id] + ); + } + } + #[test] fn parents_can_be_in_preceding_input() { for context in Context::configurations() { @@ -4535,8 +4601,8 @@ mod tests { let inscription_id = InscriptionId { txid, index: 0 }; assert_eq!( - context.index.get_parent_by_inscription_id(inscription_id), - parent_inscription_id + context.index.get_parents_by_inscription_id(inscription_id), + vec![parent_inscription_id] ); assert_eq!( @@ -4590,8 +4656,8 @@ mod tests { let inscription_id = InscriptionId { txid, index: 0 }; assert_eq!( - context.index.get_parent_by_inscription_id(inscription_id), - parent_inscription_id + context.index.get_parents_by_inscription_id(inscription_id), + vec![parent_inscription_id] ); assert_eq!( @@ -4843,8 +4909,8 @@ mod tests { assert_eq!( context .index - .get_parent_by_inscription_id(child_inscription_id), - parent_inscription_id + .get_parents_by_inscription_id(child_inscription_id), + vec![parent_inscription_id] ); assert_eq!( @@ -5724,7 +5790,7 @@ mod tests { sequence_number: 0, block_height: 2, charms: expected_charms, - parent_inscription_id: None + parent_inscription_ids: vec![] } ); diff --git a/src/index/event.rs b/src/index/event.rs index c650691158..997a70ebb4 100644 --- a/src/index/event.rs +++ b/src/index/event.rs @@ -7,7 +7,7 @@ pub enum Event { charms: u16, inscription_id: InscriptionId, location: Option, - parent_inscription_id: Option, + parent_inscription_ids: Vec, sequence_number: u32, }, InscriptionTransferred { diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index fd72902694..6c0ca8ec9d 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -26,7 +26,7 @@ enum Origin { cursed: bool, fee: u64, hidden: bool, - parent: Option, + parents: Vec, pointer: Option, reinscription: bool, unbound: bool, @@ -215,7 +215,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { cursed: curse.is_some() && !jubilant, fee: 0, hidden: inscription.payload.hidden(), - parent: inscription.payload.parents().first().map(|p| p.clone()), + parents: inscription.payload.parents(), pointer: inscription.payload.pointer(), reinscription: inscribed_offsets.get(&offset).is_some(), unbound: current_input_value == 0 @@ -253,15 +253,16 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { for flotsam in &mut floating_inscriptions { if let Flotsam { - origin: Origin::New { parent, .. }, + origin: + Origin::New { + // these are the parents the inscription claims it has + parents, + .. + }, .. } = flotsam { - if let Some(purported_parent) = parent { - if !potential_parents.contains(purported_parent) { - *parent = None; - } - } + parents.retain(|purported_parent| potential_parents.contains(purported_parent)); } } @@ -423,7 +424,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { cursed, fee, hidden, - parent, + parents, pointer: _, reinscription, unbound, @@ -496,21 +497,19 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.sat_to_sequence_number.insert(&n, &sequence_number)?; } - let parent_sequence_number = match parent { - Some(parent_id) => { - let parent_sequence_number = self - .id_to_sequence_number - .get(&parent_id.store())? - .unwrap() - .value(); - self - .sequence_number_to_children - .insert(parent_sequence_number, sequence_number)?; - - Some(parent_sequence_number) - } - None => None, - }; + let mut parent_sequence_numbers = Vec::with_capacity(parents.len()); + for parent_id in &parents { + let parent_sequence_number = self + .id_to_sequence_number + .get(&parent_id.store())? + .unwrap() + .value(); + self + .sequence_number_to_children + .insert(parent_sequence_number, sequence_number)?; + + parent_sequence_numbers.push(parent_sequence_number); + } if let Some(sender) = self.event_sender { sender.blocking_send(Event::InscriptionCreated { @@ -518,7 +517,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { charms, inscription_id, location: (!unbound).then_some(new_satpoint), - parent_inscription_id: parent, + parent_inscription_ids: parents, sequence_number, })?; } @@ -531,7 +530,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { height: self.height, id: inscription_id, inscription_number, - parents: parent_sequence_number.map_or(vec![], |p| vec![p]), + parents: parent_sequence_numbers, sat, sequence_number, timestamp: self.timestamp, diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index 2de987588a..dd8df86c09 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -130,7 +130,7 @@ impl Inscription { Tag::ContentType.encode(&mut builder, &self.content_type); Tag::ContentEncoding.encode(&mut builder, &self.content_encoding); Tag::Metaprotocol.encode(&mut builder, &self.metaprotocol); - Tag::Parent.encode(&mut builder, &self.parents.first().cloned()); + Tag::Parent.encode_array(&mut builder, &self.parents); Tag::Delegate.encode(&mut builder, &self.delegate); Tag::Pointer.encode(&mut builder, &self.pointer); Tag::Metadata.encode(&mut builder, &self.metadata); diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index 3950159069..55b6527a44 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -72,6 +72,19 @@ impl Tag { } } + pub(crate) fn encode_array(self, builder: &mut script::Builder, values: &Vec>) { + let mut tmp = script::Builder::new(); + mem::swap(&mut tmp, builder); + + for value in values { + tmp = tmp + .push_slice::<&script::PushBytes>(self.bytes().try_into().unwrap()) + .push_slice::<&script::PushBytes>(value.as_slice().try_into().unwrap()); + } + + mem::swap(&mut tmp, builder); + } + pub(crate) fn remove_field(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Option> { match self.parsing_strategy() { TagParsingStrategy::First => { From f4f54a49b818ddd871cfa193d0cf112e592947de Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 17:13:59 -0800 Subject: [PATCH 10/49] Deduplicate parents for inscription information. --- src/index.rs | 53 +++++++++++++++++++++++++++++++++ src/inscriptions/inscription.rs | 26 ++++++++++------ 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/index.rs b/src/index.rs index 0d350d8812..2c4dfdc9ce 100644 --- a/src/index.rs +++ b/src/index.rs @@ -4560,6 +4560,59 @@ mod tests { } } + #[test] + fn inscription_with_repeated_parent_tags_and_parents_has_singular_parent_entry() { + for context in Context::configurations() { + context.mine_blocks(1); + + let parent_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..Default::default() + }); + + context.mine_blocks(1); + + let parent_inscription_id = InscriptionId { + txid: parent_txid, + index: 0, + }; + + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[( + 2, + 1, + 0, + Inscription { + content_type: Some("text/plain".into()), + body: Some("hello".into()), + parents: vec![parent_inscription_id.value(), parent_inscription_id.value()], + ..Default::default() + } + .to_witness(), + )], + ..Default::default() + }); + + context.mine_blocks(1); + + let inscription_id = InscriptionId { txid, index: 0 }; + + assert_eq!( + context.index.get_parents_by_inscription_id(inscription_id), + vec![parent_inscription_id] + ); + + assert_eq!( + context + .index + .get_children_by_inscription_id(parent_inscription_id) + .unwrap(), + vec![inscription_id] + ); + } + } + + #[test] fn parents_can_be_in_preceding_input() { for context in Context::configurations() { diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index dd8df86c09..3d9341d186 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -248,15 +248,23 @@ impl Inscription { } pub(crate) fn parents(&self) -> Vec { - self - .parents - .iter() - .map(|p| { - // the option detour is a bit awkward - Self::inscription_id_field(&Some(p.clone())) - }) - .flatten() - .collect() + let mut parents: Vec = self + .parents + .iter() + .map(|p| { + // the option detour is a bit awkward + Self::inscription_id_field(&Some(p.clone())) + }) + .flatten() + .collect(); + + // remove duplicates + let mut uniques: HashSet = HashSet::with_capacity(self.parents.len()); + parents.retain(|p| { + uniques.insert(p.clone()) + }); + + parents } pub(crate) fn pointer(&self) -> Option { From 63bc32145d9bf6124b5561ba75974a6d291d0c57 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 17:19:07 -0800 Subject: [PATCH 11/49] Test that missing parents are not listed. --- src/index.rs | 101 ++++++++++++++++++++++++++++++-- src/inscriptions/inscription.rs | 20 +++---- 2 files changed, 106 insertions(+), 15 deletions(-) diff --git a/src/index.rs b/src/index.rs index 2c4dfdc9ce..c27497c75d 100644 --- a/src/index.rs +++ b/src/index.rs @@ -4557,6 +4557,13 @@ mod tests { .unwrap(), vec![inscription_id] ); + assert_eq!( + context + .index + .get_children_by_inscription_id(parent_inscription_id_b) + .unwrap(), + vec![inscription_id] + ); } } @@ -4588,7 +4595,7 @@ mod tests { parents: vec![parent_inscription_id.value(), parent_inscription_id.value()], ..Default::default() } - .to_witness(), + .to_witness(), )], ..Default::default() }); @@ -4604,14 +4611,100 @@ mod tests { assert_eq!( context - .index - .get_children_by_inscription_id(parent_inscription_id) - .unwrap(), + .index + .get_children_by_inscription_id(parent_inscription_id) + .unwrap(), vec![inscription_id] ); } } + #[test] + fn inscription_with_three_parent_tags_and_two_parents_has_two_parent_entries() { + for context in Context::configurations() { + context.mine_blocks(3); + + let parent_txid_a = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..Default::default() + }); + let parent_txid_b = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, inscription("text/plain", "world").to_witness())], + ..Default::default() + }); + let parent_txid_c = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(3, 0, 0, inscription("text/plain", "wazzup").to_witness())], + ..Default::default() + }); + + context.mine_blocks(1); + + let parent_inscription_id_a = InscriptionId { + txid: parent_txid_a, + index: 0, + }; + let parent_inscription_id_b = InscriptionId { + txid: parent_txid_b, + index: 0, + }; + let parent_inscription_id_c = InscriptionId { + txid: parent_txid_c, + index: 0, + }; + + let multi_parent_inscription = Inscription { + content_type: Some("text/plain".into()), + body: Some("hello".into()), + parents: vec![ + parent_inscription_id_a.value(), + parent_inscription_id_b.value(), + parent_inscription_id_c.value(), + ], + ..Default::default() + }; + let multi_parent_witness = multi_parent_inscription.to_witness(); + + let revealing_parent_a_input = (4, 1, 0, multi_parent_witness); + + let parent_c_input = (4, 3, 0, Witness::new()); + + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[revealing_parent_a_input, parent_c_input], + ..Default::default() + }); + + context.mine_blocks(1); + + let inscription_id = InscriptionId { txid, index: 0 }; + + assert_eq!( + context.index.get_parents_by_inscription_id(inscription_id), + vec![parent_inscription_id_a, parent_inscription_id_c] + ); + + assert_eq!( + context + .index + .get_children_by_inscription_id(parent_inscription_id_a) + .unwrap(), + vec![inscription_id] + ); + assert_eq!( + context + .index + .get_children_by_inscription_id(parent_inscription_id_b) + .unwrap(), + vec![] + ); + assert_eq!( + context + .index + .get_children_by_inscription_id(parent_inscription_id_c) + .unwrap(), + vec![inscription_id] + ); + } + } #[test] fn parents_can_be_in_preceding_input() { diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index 3d9341d186..7338eab4ea 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -249,20 +249,18 @@ impl Inscription { pub(crate) fn parents(&self) -> Vec { let mut parents: Vec = self - .parents - .iter() - .map(|p| { - // the option detour is a bit awkward - Self::inscription_id_field(&Some(p.clone())) - }) - .flatten() - .collect(); + .parents + .iter() + .map(|p| { + // the option detour is a bit awkward + Self::inscription_id_field(&Some(p.clone())) + }) + .flatten() + .collect(); // remove duplicates let mut uniques: HashSet = HashSet::with_capacity(self.parents.len()); - parents.retain(|p| { - uniques.insert(p.clone()) - }); + parents.retain(|p| uniques.insert(p.clone())); parents } From d5e7156d7450d737490fafc41f48f4a084f09cdd Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 17:25:11 -0800 Subject: [PATCH 12/49] Test filtering malformed parent ids. --- src/index.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/src/index.rs b/src/index.rs index c27497c75d..e5dd20e194 100644 --- a/src/index.rs +++ b/src/index.rs @@ -4706,6 +4706,99 @@ mod tests { } } + #[test] + fn inscription_with_valid_and_malformed_parent_tags_only_lists_valid_entries() { + for context in Context::configurations() { + context.mine_blocks(3); + + let parent_txid_a = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..Default::default() + }); + let parent_txid_b = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, inscription("text/plain", "world").to_witness())], + ..Default::default() + }); + let parent_txid_c = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(3, 0, 0, inscription("text/plain", "wazzup").to_witness())], + ..Default::default() + }); + + context.mine_blocks(1); + + let parent_inscription_id_a = InscriptionId { + txid: parent_txid_a, + index: 0, + }; + let parent_inscription_id_b = InscriptionId { + txid: parent_txid_b, + index: 0, + }; + let parent_inscription_id_c = InscriptionId { + txid: parent_txid_c, + index: 0, + }; + + let malformed_inscription_id_b = parent_inscription_id_b + .value() + .into_iter() + .chain(iter::once(0)) + .collect(); + + let multi_parent_inscription = Inscription { + content_type: Some("text/plain".into()), + body: Some("hello".into()), + parents: vec![ + parent_inscription_id_a.value(), + malformed_inscription_id_b, + parent_inscription_id_c.value(), + ], + ..Default::default() + }; + let multi_parent_witness = multi_parent_inscription.to_witness(); + + let revealing_parent_a_input = (4, 1, 0, multi_parent_witness); + let parent_b_input = (4, 2, 0, Witness::new()); + let parent_c_input = (4, 3, 0, Witness::new()); + + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[revealing_parent_a_input, parent_b_input, parent_c_input], + ..Default::default() + }); + + context.mine_blocks(1); + + let inscription_id = InscriptionId { txid, index: 0 }; + + assert_eq!( + context.index.get_parents_by_inscription_id(inscription_id), + vec![parent_inscription_id_a, parent_inscription_id_c] + ); + + assert_eq!( + context + .index + .get_children_by_inscription_id(parent_inscription_id_a) + .unwrap(), + vec![inscription_id] + ); + assert_eq!( + context + .index + .get_children_by_inscription_id(parent_inscription_id_b) + .unwrap(), + vec![] + ); + assert_eq!( + context + .index + .get_children_by_inscription_id(parent_inscription_id_c) + .unwrap(), + vec![inscription_id] + ); + } + } + #[test] fn parents_can_be_in_preceding_input() { for context in Context::configurations() { From b51dcfab4f66ef7505115e6dbd399c02aa7cc29c Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 6 Mar 2024 18:54:04 -0800 Subject: [PATCH 13/49] Stash --- src/index.rs | 14 ++++++++++ src/subcommand/server.rs | 55 +++++++++++++++++++++++++++++++++++--- src/templates.rs | 2 ++ templates/inscription.html | 7 +++-- 4 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/index.rs b/src/index.rs index e5dd20e194..053032c01c 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1140,6 +1140,20 @@ impl Index { Ok((children, more)) } + pub(crate) fn get_parents_by_sequence_number_paginated( + &self, + sequence_number: u32, + page_size: usize, + page_index: usize, + ) -> Result<(Vec, bool)> { + let rtx = self.database.begin_read()?; + + let sequence_number_to_entry = rtx.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY)?; + + // TODO + Ok((Vec::new(), false)) + } + pub(crate) fn get_etching(&self, txid: Txid) -> Result> { let rtx = self.database.begin_read()?; diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index e33ea55336..a1b7e1b756 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -10,9 +10,10 @@ use { templates::{ BlockHtml, BlocksHtml, ChildrenHtml, ClockSvg, CollectionsHtml, HomeHtml, InputHtml, InscriptionHtml, InscriptionsBlockHtml, InscriptionsHtml, OutputHtml, PageContent, PageHtml, - PreviewAudioHtml, PreviewCodeHtml, PreviewFontHtml, PreviewImageHtml, PreviewMarkdownHtml, - PreviewModelHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, PreviewVideoHtml, - RangeHtml, RareTxt, RuneBalancesHtml, RuneHtml, RunesHtml, SatHtml, TransactionHtml, + ParentsHtml, PreviewAudioHtml, PreviewCodeHtml, PreviewFontHtml, PreviewImageHtml, + PreviewMarkdownHtml, PreviewModelHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, + PreviewVideoHtml, RangeHtml, RareTxt, RuneBalancesHtml, RuneHtml, RunesHtml, SatHtml, + TransactionHtml, }, }, axum::{ @@ -224,6 +225,7 @@ impl Server { .route("/install.sh", get(Self::install_script)) .route("/ordinal/:sat", get(Self::ordinal)) .route("/output/:output", get(Self::output)) + .route("/parents/:inscription_id", get(Self::parents)) .route("/preview/:inscription_id", get(Self::preview)) .route("/r/blockhash", get(Self::block_hash_json)) .route( @@ -1720,6 +1722,53 @@ impl Server { }) } + async fn parents( + Extension(server_config): Extension>, + Extension(index): Extension>, + Path(inscription_id): Path, + ) -> ServerResult { + Self::parents_paginated( + Extension(server_config), + Extension(index), + Path((inscription_id, 0)), + ) + .await + } + + async fn parents_paginated( + Extension(server_config): Extension>, + Extension(index): Extension>, + Path((child, page)): Path<(InscriptionId, usize)>, + ) -> ServerResult { + //TODO + task::block_in_place(|| { + let entry = index + .get_inscription_entry(child)? + .ok_or_not_found(|| format!("inscription {child}"))?; + + let child_number = entry.inscription_number; + + let (parents, more_parents) = + index.get_parents_by_sequence_number_paginated(entry.sequence_number, 100, page)?; + + let prev_page = page.checked_sub(1); + + let next_page = more_parents.then_some(page + 1); + + Ok( + ParentsHtml { + child, + child_number, + parents, + prev_page, + next_page, + } + .page(server_config) + .into_response(), + ) + }) + } + async fn sat_inscriptions( Extension(index): Extension>, Path(sat): Path, diff --git a/src/templates.rs b/src/templates.rs index e0474c34a3..5a21daf3ea 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -13,6 +13,7 @@ pub(crate) use { inscriptions_block::InscriptionsBlockHtml, metadata::MetadataHtml, output::OutputHtml, + parents::ParentsHtml, preview::{ PreviewAudioHtml, PreviewCodeHtml, PreviewFontHtml, PreviewImageHtml, PreviewMarkdownHtml, PreviewModelHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, PreviewVideoHtml, @@ -42,6 +43,7 @@ pub mod inscriptions; mod inscriptions_block; mod metadata; pub mod output; +mod parents; mod preview; mod range; mod rare; diff --git a/templates/inscription.html b/templates/inscription.html index 5128329e88..00bb51951a 100644 --- a/templates/inscription.html +++ b/templates/inscription.html @@ -30,11 +30,14 @@

Inscription {{ self.inscription_number }}

{{ self.inscription_id }}
%% if !&self.parents.is_empty() {
parents
-%% } -%% for parent in &self.parents {
+%% for parent in &self.parents { {{Iframe::thumbnail(*parent)}} +%% } +
+
%% } From 01883e7d960377d250390e0e1b7a11f80aa04fa0 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Wed, 6 Mar 2024 18:54:15 -0800 Subject: [PATCH 14/49] New templates --- src/templates/parents.rs | 71 ++++++++++++++++++++++++++++++++++++++++ templates/parents.html | 22 +++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/templates/parents.rs create mode 100644 templates/parents.html diff --git a/src/templates/parents.rs b/src/templates/parents.rs new file mode 100644 index 0000000000..5c87d5bf74 --- /dev/null +++ b/src/templates/parents.rs @@ -0,0 +1,71 @@ +use super::*; + +#[derive(Boilerplate)] +pub(crate) struct ParentsHtml { + pub(crate) child: InscriptionId, + pub(crate) child_number: i32, + pub(crate) parents: Vec, + pub(crate) prev_page: Option, + pub(crate) next_page: Option, +} + +impl PageContent for ParentsHtml { + fn title(&self) -> String { + format!("Inscription {} Parents", self.child_number) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn without_prev_and_next() { + assert_regex_match!( + ParentsHtml { + child: inscription_id(1), + child_number: 0, + parents: vec![inscription_id(2), inscription_id(3)], + prev_page: None, + next_page: None, + }, + " +

Inscription 0 Parents

+
+ + +
+ .* + prev + next + .* + " + .unindent() + ); + } + + #[test] + fn with_prev_and_next() { + assert_regex_match!( + ParentsHtml { + child: inscription_id(1), + child_number: 0, + parents: vec![inscription_id(2), inscription_id(3)], + next_page: Some(3), + prev_page: Some(1), + }, + " +

Inscription 0 Parents

+
+ + +
+ .* + + + .* + " + .unindent() + ); + } +} diff --git a/templates/parents.html b/templates/parents.html new file mode 100644 index 0000000000..0f9c56d3b8 --- /dev/null +++ b/templates/parents.html @@ -0,0 +1,22 @@ +

Inscription {{ self.child_number }} Parents

+%% if self.parents.is_empty() { +

No parents

+%% } else { +
+%% for id in &self.parents { + {{ Iframe::thumbnail(*id) }} +%% } +
+
+%% if let Some(prev_page) = &self.prev_page { + +%% } else { +prev +%% } +%% if let Some(next_page) = &self.next_page { + +%% } else { +next +%% } +
+%% } From e881416c1b086026779c227eb7984a956600b119 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 23:10:08 -0800 Subject: [PATCH 15/49] Optimize duplicate parent detection. --- src/inscriptions/inscription.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index 7338eab4ea..73428c366e 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -248,21 +248,19 @@ impl Inscription { } pub(crate) fn parents(&self) -> Vec { - let mut parents: Vec = self + let mut unique_parents: HashSet> = HashSet::with_capacity(self.parents.len()); + self .parents .iter() .map(|p| { + if !unique_parents.insert(p.clone()) { + return None; + } // the option detour is a bit awkward Self::inscription_id_field(&Some(p.clone())) }) .flatten() - .collect(); - - // remove duplicates - let mut uniques: HashSet = HashSet::with_capacity(self.parents.len()); - parents.retain(|p| uniques.insert(p.clone())); - - parents + .collect() } pub(crate) fn pointer(&self) -> Option { From dc5a891d2a4faf2255b4c142148e0ba90270011d Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Wed, 6 Mar 2024 23:12:10 -0800 Subject: [PATCH 16/49] Renaming parsing strategy to codec. --- src/inscriptions/tag.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index 55b6527a44..ce8863f9c1 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -16,18 +16,18 @@ pub(crate) enum Tag { Nop, } -enum TagParsingStrategy { +enum TagCodecStrategy { First, Chunked, Array, } impl Tag { - fn parsing_strategy(self) -> TagParsingStrategy { + fn parsing_strategy(self) -> TagCodecStrategy { match self { - Tag::Metadata => TagParsingStrategy::Chunked, - Tag::Parent => TagParsingStrategy::Array, - _ => TagParsingStrategy::First, + Tag::Metadata => TagCodecStrategy::Chunked, + Tag::Parent => TagCodecStrategy::Array, + _ => TagCodecStrategy::First, } } @@ -52,12 +52,12 @@ impl Tag { mem::swap(&mut tmp, builder); match self.parsing_strategy() { - TagParsingStrategy::First | TagParsingStrategy::Array => { + TagCodecStrategy::First | TagCodecStrategy::Array => { tmp = tmp .push_slice::<&script::PushBytes>(self.bytes().try_into().unwrap()) .push_slice::<&script::PushBytes>(value.as_slice().try_into().unwrap()); } - TagParsingStrategy::Chunked => { + TagCodecStrategy::Chunked => { for chunk in value.chunks(MAX_SCRIPT_ELEMENT_SIZE) { tmp = tmp .push_slice::<&script::PushBytes>(self.bytes().try_into().unwrap()) @@ -87,7 +87,7 @@ impl Tag { pub(crate) fn remove_field(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Option> { match self.parsing_strategy() { - TagParsingStrategy::First => { + TagCodecStrategy::First => { let values = fields.get_mut(self.bytes())?; if values.is_empty() { @@ -102,7 +102,7 @@ impl Tag { Some(value) } } - TagParsingStrategy::Chunked => { + TagCodecStrategy::Chunked => { let value = fields.remove(self.bytes())?; if value.is_empty() { @@ -111,7 +111,7 @@ impl Tag { Some(value.into_iter().flatten().cloned().collect()) } } - TagParsingStrategy::Array => { + TagCodecStrategy::Array => { panic!("Array-type fields must not be removed as a simple byte array.") } } From 3993fe25852c04c385ce217c23810264fc15f6d0 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Thu, 7 Mar 2024 15:58:36 -0800 Subject: [PATCH 17/49] Rename potential_parents and purported_parent to not have alliterations for improved legibility. --- src/index/updater/inscription_updater.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 6c0ca8ec9d..5ff55778f7 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -246,23 +246,21 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.transaction_buffer.clear(); } - let potential_parents = floating_inscriptions + let eligible_parents = floating_inscriptions .iter() .map(|flotsam| flotsam.inscription_id) .collect::>(); for flotsam in &mut floating_inscriptions { if let Flotsam { - origin: - Origin::New { - // these are the parents the inscription claims it has - parents, - .. - }, + origin: Origin::New { + parents: claimed_parents, + .. + }, .. } = flotsam { - parents.retain(|purported_parent| potential_parents.contains(purported_parent)); + claimed_parents.retain(|p| eligible_parents.contains(p)); } } From 91b5d801418feef52d94098685b6ce3ea69d1e6b Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Thu, 7 Mar 2024 16:00:37 -0800 Subject: [PATCH 18/49] Add some comments. --- src/inscriptions/inscription.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index 73428c366e..1f3a1db422 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -247,6 +247,7 @@ impl Inscription { str::from_utf8(self.metaprotocol.as_ref()?).ok() } + /// Returns a deduplicated list of parent inscription IDs the inscription claims to have. pub(crate) fn parents(&self) -> Vec { let mut unique_parents: HashSet> = HashSet::with_capacity(self.parents.len()); self From 08ddbe1b8503fc71c3f942f12f2742275445aa1c Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Thu, 7 Mar 2024 16:17:00 -0800 Subject: [PATCH 19/49] Enforce deduplication for non-deterministic parent encoding. --- src/index.rs | 58 +++++++++++++++++++++++++++++++++ src/inscriptions/inscription.rs | 10 +++--- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/index.rs b/src/index.rs index 053032c01c..5698b48c21 100644 --- a/src/index.rs +++ b/src/index.rs @@ -4633,6 +4633,64 @@ mod tests { } } + #[test] + fn inscription_with_distinct_parent_tag_encodings_for_same_parent_has_singular_parent_entry() { + for context in Context::configurations() { + context.mine_blocks(1); + + let parent_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..Default::default() + }); + + context.mine_blocks(1); + + let parent_inscription_id = InscriptionId { + txid: parent_txid, + index: 0, + }; + + let trailing_zero_inscription_id: Vec = parent_inscription_id + .value() + .into_iter() + .chain(vec![0, 0, 0, 0]) + .collect(); + + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[( + 2, + 1, + 0, + Inscription { + content_type: Some("text/plain".into()), + body: Some("hello".into()), + parents: vec![parent_inscription_id.value(), trailing_zero_inscription_id], + ..Default::default() + } + .to_witness(), + )], + ..Default::default() + }); + + context.mine_blocks(1); + + let inscription_id = InscriptionId { txid, index: 0 }; + + assert_eq!( + context.index.get_parents_by_inscription_id(inscription_id), + vec![parent_inscription_id] + ); + + assert_eq!( + context + .index + .get_children_by_inscription_id(parent_inscription_id) + .unwrap(), + vec![inscription_id] + ); + } + } + #[test] fn inscription_with_three_parent_tags_and_two_parents_has_two_parent_entries() { for context in Context::configurations() { diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index 1f3a1db422..bdf9f64e1c 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -249,16 +249,18 @@ impl Inscription { /// Returns a deduplicated list of parent inscription IDs the inscription claims to have. pub(crate) fn parents(&self) -> Vec { - let mut unique_parents: HashSet> = HashSet::with_capacity(self.parents.len()); + let mut unique_parents: HashSet = HashSet::with_capacity(self.parents.len()); self .parents .iter() .map(|p| { - if !unique_parents.insert(p.clone()) { + let Some(parent_id) = Self::inscription_id_field(&Some(p.clone())) else { + return None; + }; + if !unique_parents.insert(parent_id) { return None; } - // the option detour is a bit awkward - Self::inscription_id_field(&Some(p.clone())) + Some(parent_id) }) .flatten() .collect() From d19dec2ad389409ab30b923d4a15c14be3cf04f8 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Thu, 7 Mar 2024 17:25:00 -0800 Subject: [PATCH 20/49] Fix unwrap_or where default is possible. --- src/inscriptions/tag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index ce8863f9c1..bca260f561 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -118,7 +118,7 @@ impl Tag { } pub(crate) fn remove_array_field(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Vec> { - let values = fields.remove(self.bytes()).unwrap_or(vec![]); + let values = fields.remove(self.bytes()).unwrap_or_default(); // .cloned() doesn't work for Vec<&[u8]> values.into_iter().map(|v| v.to_vec()).collect() } From 7bb47146c2ea21a1741d6a06a2b9d2fea0de9557 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Thu, 7 Mar 2024 17:25:18 -0800 Subject: [PATCH 21/49] Replace map().flatten() with filter_map() --- src/inscriptions/inscription.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index bdf9f64e1c..eb262c81bb 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -253,7 +253,7 @@ impl Inscription { self .parents .iter() - .map(|p| { + .filter_map(|p| { let Some(parent_id) = Self::inscription_id_field(&Some(p.clone())) else { return None; }; @@ -262,7 +262,6 @@ impl Inscription { } Some(parent_id) }) - .flatten() .collect() } From 7ad6394690948d9cbea06585c6a672d2ac76342b Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Thu, 7 Mar 2024 17:25:39 -0800 Subject: [PATCH 22/49] Make CI shut up about temporarily unused variables. --- src/index.rs | 8 ++++---- src/wallet/inscribe/batch.rs | 10 ++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/index.rs b/src/index.rs index 5698b48c21..2f13580a5c 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1142,13 +1142,13 @@ impl Index { pub(crate) fn get_parents_by_sequence_number_paginated( &self, - sequence_number: u32, - page_size: usize, - page_index: usize, + _sequence_number: u32, + _page_size: usize, + _page_index: usize, ) -> Result<(Vec, bool)> { let rtx = self.database.begin_read()?; - let sequence_number_to_entry = rtx.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY)?; + let _sequence_number_to_entry = rtx.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY)?; // TODO Ok((Vec::new(), false)) diff --git a/src/wallet/inscribe/batch.rs b/src/wallet/inscribe/batch.rs index 43cdf3d512..310dac5047 100644 --- a/src/wallet/inscribe/batch.rs +++ b/src/wallet/inscribe/batch.rs @@ -220,12 +220,10 @@ impl Batch { change: [Address; 2], ) -> Result<(Transaction, Transaction, TweakedKeyPair, u64)> { if let Some(parent_info) = &self.parent_info { - assert!(self.inscriptions.iter().all(|inscription| inscription - .parents() - .first() - .unwrap() - .clone() - == parent_info.id)) + assert!(self + .inscriptions + .iter() + .all(|inscription| { *inscription.parents().first().unwrap() == parent_info.id })) } match self.mode { From 8028be8c928f90890aaba2f030db199813c0f842 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Fri, 8 Mar 2024 15:10:05 -0800 Subject: [PATCH 23/49] Fix plural in variable name. --- src/index.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/index.rs b/src/index.rs index 2f13580a5c..6b2d8ae738 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1081,7 +1081,9 @@ impl Index { .open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY) .unwrap(); - let parents_sequences = InscriptionEntry::load( + // we need to introduce a temporary variable to appease the borrow checker + // a historically tried and true strategy + let parent_sequences = InscriptionEntry::load( sequence_number_to_inscription_entry .get(sequence_number) .unwrap() @@ -1090,7 +1092,7 @@ impl Index { ) .parents; - parents_sequences + parent_sequences .into_iter() .map(|parent_sequence_number| { InscriptionEntry::load( From a2a925f67b443a7a7227986730fdd9e3a883d8c7 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Mon, 11 Mar 2024 19:02:25 -0700 Subject: [PATCH 24/49] Fix test --- src/templates/inscription.rs | 9 ++++++--- templates/inscription.html | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/templates/inscription.rs b/src/templates/inscription.rs index 2a01896bb5..d0def05caf 100644 --- a/src/templates/inscription.rs +++ b/src/templates/inscription.rs @@ -225,14 +225,17 @@ mod tests {
-
id
-
1{64}i1
parents
+
+ all +
+
id
+
1{64}i1
preview
link
content
@@ -258,7 +261,7 @@ mod tests {
ethereum teleburn address
0xa1DfBd1C519B9323FD7Fd8e498Ac16c2E502F059
- " +" .unindent() ); } diff --git a/templates/inscription.html b/templates/inscription.html index 51ce98fa4b..2e54991985 100644 --- a/templates/inscription.html +++ b/templates/inscription.html @@ -13,34 +13,34 @@

Inscription {{ self.number }}

%% }
-%% if !self.children.is_empty() { -
children
+%% if !&self.parents.is_empty() { +
parents
-%% for id in &self.children { - {{Iframe::thumbnail(*id)}} +%% for parent in &self.parents { + {{Iframe::thumbnail(*parent)}} %% }
- all + all
%% } -
id
-
{{ self.id }}
-%% if !&self.parents.is_empty() { -
parents
+%% if !self.children.is_empty() { +
children
-%% for parent in &self.parents { - {{Iframe::thumbnail(*parent)}} +%% for id in &self.children { + {{Iframe::thumbnail(*id)}} %% }
- all + all
%% } +
id
+
{{ self.id }}
%% if self.charms != 0 {
charms
From 47bd4e09f6b5cc1dff78173aee2f61c008d59678 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Mon, 11 Mar 2024 19:40:22 -0700 Subject: [PATCH 25/49] Add parent pagination --- src/index.rs | 29 +++++++++++++++++++++++------ src/subcommand/server.rs | 17 +++++++---------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/index.rs b/src/index.rs index 4a8ce54313..3c27e84a71 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1151,16 +1151,33 @@ impl Index { pub(crate) fn get_parents_by_sequence_number_paginated( &self, - _sequence_number: u32, - _page_size: usize, - _page_index: usize, + parent_sequence_numbers: Vec, + page_size: usize, + page_index: usize, ) -> Result<(Vec, bool)> { let rtx = self.database.begin_read()?; - let _sequence_number_to_entry = rtx.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY)?; + let sequence_number_to_entry = rtx.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY)?; + + let mut parents = parent_sequence_numbers + .iter() + .skip(page_index * page_size) + .take(page_size.saturating_add(1)) + .map(|sequence_number| { + sequence_number_to_entry + .get(sequence_number) + .map(|entry| InscriptionEntry::load(entry.unwrap().value()).id) + .map_err(|err| err.into()) + }) + .collect::>>()?; + + let more_parents = parents.len() > 100; + + if more_parents { + parents.pop(); + } - // TODO - Ok((Vec::new(), false)) + Ok((parents, more_parents)) } pub(crate) fn get_etching(&self, txid: Txid) -> Result> { diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index d9041a9f10..a532f02ee5 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1746,18 +1746,15 @@ impl Server { async fn parents_paginated( Extension(server_config): Extension>, Extension(index): Extension>, - Path((child, page)): Path<(InscriptionId, usize)>, + Path((child_id, page)): Path<(InscriptionId, usize)>, ) -> ServerResult { - //TODO task::block_in_place(|| { - let entry = index - .get_inscription_entry(child)? - .ok_or_not_found(|| format!("inscription {child}"))?; - - let child_number = entry.inscription_number; + let child_entry = index + .get_inscription_entry(child_id)? + .ok_or_not_found(|| format!("inscription {child_id}"))?; let (parents, more_parents) = - index.get_parents_by_sequence_number_paginated(entry.sequence_number, 100, page)?; + index.get_parents_by_sequence_number_paginated(child_entry.parents, 100, page)?; let prev_page = page.checked_sub(1); @@ -1765,8 +1762,8 @@ impl Server { Ok( ParentsHtml { - child, - child_number, + child: child_id, + child_number: child_entry.inscription_number, parents, prev_page, next_page, From 1d93ee84f73d304ef1d82f3f2baba79bc89a5952 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Mon, 11 Mar 2024 19:42:18 -0700 Subject: [PATCH 26/49] Amend --- src/index/entry.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index/entry.rs b/src/index/entry.rs index bc7bb31cbb..d8e7e6c728 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -211,7 +211,7 @@ pub(crate) type InscriptionEntryValue = ( u32, // height InscriptionIdValue, // inscription id i32, // inscription number - Vec, // parent + Vec, // parents Option, // sat u32, // sequence number u32, // timestamp From 228997fd4f4a9ae9d0ba607dd314f187abe29d70 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Mon, 11 Mar 2024 19:51:08 -0700 Subject: [PATCH 27/49] Small things --- src/index/updater/inscription_updater.rs | 1 + src/inscriptions/envelope.rs | 1 - src/inscriptions/inscription.rs | 12 ++++++------ src/subcommand/wallet/inscribe.rs | 2 +- src/wallet/inscribe/batch_file.rs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 5ff55778f7..cfdb8ba10c 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -502,6 +502,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { .get(&parent_id.store())? .unwrap() .value(); + self .sequence_number_to_children .insert(parent_sequence_number, sequence_number)?; diff --git a/src/inscriptions/envelope.rs b/src/inscriptions/envelope.rs index 876173047e..2a2b774320 100644 --- a/src/inscriptions/envelope.rs +++ b/src/inscriptions/envelope.rs @@ -53,7 +53,6 @@ impl From for ParsedEnvelope { let delegate = Tag::Delegate.remove_field(&mut fields); let metadata = Tag::Metadata.remove_field(&mut fields); let metaprotocol = Tag::Metaprotocol.remove_field(&mut fields); - // let parent = Tag::Parent.remove_field(&mut fields); let parents = Tag::Parent.remove_array_field(&mut fields); let pointer = Tag::Pointer.remove_field(&mut fields); diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index eb262c81bb..bbd15aeddd 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -42,7 +42,7 @@ impl Inscription { delegate: Option, metadata: Option>, metaprotocol: Option, - parent: Option, + parents: Vec, path: impl AsRef, pointer: Option, ) -> Result { @@ -102,7 +102,7 @@ impl Inscription { delegate: delegate.map(|delegate| delegate.value()), metadata, metaprotocol: metaprotocol.map(|metaprotocol| metaprotocol.into_bytes()), - parents: parent.map_or(vec![], |parent| vec![parent.value()]), + parents: parents.iter().map(|parent| parent.value()).collect(), pointer: pointer.map(Self::pointer_value), ..Default::default() }) @@ -752,7 +752,7 @@ mod tests { None, None, None, - None, + Vec::new(), file.path(), None, ) @@ -766,7 +766,7 @@ mod tests { None, None, None, - None, + Vec::new(), file.path(), Some(0), ) @@ -780,7 +780,7 @@ mod tests { None, None, None, - None, + Vec::new(), file.path(), Some(1), ) @@ -794,7 +794,7 @@ mod tests { None, None, None, - None, + Vec::new(), file.path(), Some(256), ) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 1ecb64ae53..e4bfaa2834 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -109,7 +109,7 @@ impl Inscribe { self.delegate, metadata, self.metaprotocol, - self.parent, + self.parent.map_or(Vec::new(), |parent| vec![parent]), file, None, )?]; diff --git a/src/wallet/inscribe/batch_file.rs b/src/wallet/inscribe/batch_file.rs index d7472f6417..7c47cd056f 100644 --- a/src/wallet/inscribe/batch_file.rs +++ b/src/wallet/inscribe/batch_file.rs @@ -134,7 +134,7 @@ impl Batchfile { entry.delegate, entry.metadata()?, entry.metaprotocol.clone(), - self.parent, + self.parent.map_or(Vec::new(), |parent| vec![parent]), &entry.file, Some(pointer), )?); From a78f841cf1845c782a4fbd5c0efab257b484fe25 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Mon, 11 Mar 2024 20:01:04 -0700 Subject: [PATCH 28/49] Minor things --- src/index.rs | 2 +- src/index/updater/inscription_updater.rs | 2 +- tests/decode.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.rs b/src/index.rs index 3c27e84a71..43b52a364e 100644 --- a/src/index.rs +++ b/src/index.rs @@ -6128,7 +6128,7 @@ mod tests { sequence_number: 0, block_height: 2, charms: expected_charms, - parent_inscription_ids: vec![] + parent_inscription_ids: Vec::new(), } ); diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index cfdb8ba10c..5884f937f6 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -495,7 +495,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.sat_to_sequence_number.insert(&n, &sequence_number)?; } - let mut parent_sequence_numbers = Vec::with_capacity(parents.len()); + let mut parent_sequence_numbers = Vec::new(); for parent_id in &parents { let parent_sequence_number = self .id_to_sequence_number diff --git a/tests/decode.rs b/tests/decode.rs index e503db9067..b3500a47f3 100644 --- a/tests/decode.rs +++ b/tests/decode.rs @@ -136,7 +136,7 @@ fn compact() { incomplete_field: false, metadata: None, metaprotocol: None, - parents: vec![], + parents: Vec::new(), pointer: None, unrecognized_even_field: false, }], From b638d220819f16390d2b387698da1233d86c232d Mon Sep 17 00:00:00 2001 From: Arik Date: Mon, 11 Mar 2024 20:29:54 -0700 Subject: [PATCH 29/49] Update src/inscriptions/tag.rs Co-authored-by: raph --- src/inscriptions/tag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index 3c284ac7a9..3634e76393 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -122,7 +122,7 @@ impl Tag { pub(crate) fn remove_array_field(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Vec> { let values = fields.remove(self.bytes()).unwrap_or_default(); - // .cloned() doesn't work for Vec<&[u8]> + values.into_iter().map(|v| v.to_vec()).collect() } } From a0471b95564477a40608bfe2a4d30911008a37ee Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Mon, 11 Mar 2024 20:32:03 -0700 Subject: [PATCH 30/49] Fix Github's whitespace. --- src/inscriptions/tag.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index 3634e76393..4dd7590708 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -122,7 +122,6 @@ impl Tag { pub(crate) fn remove_array_field(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Vec> { let values = fields.remove(self.bytes()).unwrap_or_default(); - values.into_iter().map(|v| v.to_vec()).collect() } } From 789876215ce65edf37212e058740f79585f1bb08 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Thu, 14 Mar 2024 18:17:56 -0700 Subject: [PATCH 31/49] Fix formating. --- src/subcommand/server.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 9b2890bf10..bca5cb5ae8 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -8,9 +8,10 @@ use { crate::templates::{ BlockHtml, BlocksHtml, ChildrenHtml, ClockSvg, CollectionsHtml, HomeHtml, InputHtml, InscriptionHtml, InscriptionsBlockHtml, InscriptionsHtml, OutputHtml, PageContent, PageHtml, - ParentsHtml, PreviewAudioHtml, PreviewCodeHtml, PreviewFontHtml, PreviewImageHtml, PreviewMarkdownHtml, - PreviewModelHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, PreviewVideoHtml, - RangeHtml, RareTxt, RuneBalancesHtml, RuneHtml, RunesHtml, SatHtml, TransactionHtml, + ParentsHtml, PreviewAudioHtml, PreviewCodeHtml, PreviewFontHtml, PreviewImageHtml, + PreviewMarkdownHtml, PreviewModelHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, + PreviewVideoHtml, RangeHtml, RareTxt, RuneBalancesHtml, RuneHtml, RunesHtml, SatHtml, + TransactionHtml, }, axum::{ body, From 44f963b583499c58c2524fbeb0ef034c8eb101d2 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 15 Mar 2024 14:49:41 -0700 Subject: [PATCH 32/49] Amend --- src/index/updater/inscription_updater.rs | 6 +++--- src/subcommand/server.rs | 4 ++-- src/templates/parents.rs | 14 +++++++------- templates/parents.html | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 5884f937f6..871d106596 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -246,7 +246,7 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.transaction_buffer.clear(); } - let eligible_parents = floating_inscriptions + let potential_parents = floating_inscriptions .iter() .map(|flotsam| flotsam.inscription_id) .collect::>(); @@ -254,13 +254,13 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { for flotsam in &mut floating_inscriptions { if let Flotsam { origin: Origin::New { - parents: claimed_parents, + parents: purported_parents, .. }, .. } = flotsam { - claimed_parents.retain(|p| eligible_parents.contains(p)); + purported_parents.retain(|p| potential_parents.contains(p)); } } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index bca5cb5ae8..33fbbb6fe9 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1768,8 +1768,8 @@ impl Server { Ok( ParentsHtml { - child: child_id, - child_number: child_entry.inscription_number, + id: child_id, + number: child_entry.inscription_number, parents, prev_page, next_page, diff --git a/src/templates/parents.rs b/src/templates/parents.rs index 5c87d5bf74..e142c126d4 100644 --- a/src/templates/parents.rs +++ b/src/templates/parents.rs @@ -2,8 +2,8 @@ use super::*; #[derive(Boilerplate)] pub(crate) struct ParentsHtml { - pub(crate) child: InscriptionId, - pub(crate) child_number: i32, + pub(crate) id: InscriptionId, + pub(crate) number: i32, pub(crate) parents: Vec, pub(crate) prev_page: Option, pub(crate) next_page: Option, @@ -11,7 +11,7 @@ pub(crate) struct ParentsHtml { impl PageContent for ParentsHtml { fn title(&self) -> String { - format!("Inscription {} Parents", self.child_number) + format!("Inscription {} Parents", self.number) } } @@ -23,8 +23,8 @@ mod tests { fn without_prev_and_next() { assert_regex_match!( ParentsHtml { - child: inscription_id(1), - child_number: 0, + id: inscription_id(1), + number: 0, parents: vec![inscription_id(2), inscription_id(3)], prev_page: None, next_page: None, @@ -48,8 +48,8 @@ mod tests { fn with_prev_and_next() { assert_regex_match!( ParentsHtml { - child: inscription_id(1), - child_number: 0, + id: inscription_id(1), + number: 0, parents: vec![inscription_id(2), inscription_id(3)], next_page: Some(3), prev_page: Some(1), diff --git a/templates/parents.html b/templates/parents.html index 0f9c56d3b8..9612de657a 100644 --- a/templates/parents.html +++ b/templates/parents.html @@ -1,4 +1,4 @@ -

Inscription {{ self.child_number }} Parents

+

Inscription {{ self.number }} Parents

%% if self.parents.is_empty() {

No parents

%% } else { @@ -9,12 +9,12 @@

No parents

%% if let Some(prev_page) = &self.prev_page { - + %% } else { prev %% } %% if let Some(next_page) = &self.next_page { - + %% } else { next %% } From 8aafcc4f8662ea50585fda230ac805e18be8fa76 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 15 Mar 2024 14:52:13 -0700 Subject: [PATCH 33/49] Vec::new() --- src/index.rs | 6 +++--- src/inscriptions/envelope.rs | 2 +- src/inscriptions/inscription.rs | 4 ++-- src/subcommand/server.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/index.rs b/src/index.rs index 252fc90449..23957f92c2 100644 --- a/src/index.rs +++ b/src/index.rs @@ -4176,7 +4176,7 @@ mod tests { for context in Context::configurations() { context.mine_blocks(1); - let mut inscription_ids = vec![]; + let mut inscription_ids = Vec::new(); for i in 1..=21 { let txid = context.rpc_server.broadcast_tx(TransactionTemplate { inputs: &[( @@ -4783,7 +4783,7 @@ mod tests { .index .get_children_by_inscription_id(parent_inscription_id_b) .unwrap(), - vec![] + Vec::new() ); assert_eq!( context @@ -4876,7 +4876,7 @@ mod tests { .index .get_children_by_inscription_id(parent_inscription_id_b) .unwrap(), - vec![] + Vec::new() ); assert_eq!( context diff --git a/src/inscriptions/envelope.rs b/src/inscriptions/envelope.rs index 2a2b774320..d7d199a049 100644 --- a/src/inscriptions/envelope.rs +++ b/src/inscriptions/envelope.rs @@ -855,7 +855,7 @@ mod tests { parse(&[envelope(&[&PROTOCOL_ID, Tag::Metadata.bytes(), &[]])]), vec![ParsedEnvelope { payload: Inscription { - metadata: Some(vec![]), + metadata: Some(Vec::new()), ..Default::default() }, ..Default::default() diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index bbd15aeddd..abce94a214 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -435,7 +435,7 @@ mod tests { #[test] fn inscription_with_no_parent_field_has_no_parent() { assert!(Inscription { - parents: vec![], + parents: Vec::new(), ..Default::default() } .parents() @@ -445,7 +445,7 @@ mod tests { #[test] fn inscription_with_parent_field_shorter_than_txid_length_has_no_parent() { assert!(Inscription { - parents: vec![vec![]], + parents: vec![Vec::new()], ..Default::default() } .parents() diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 33fbbb6fe9..d4e2312ec0 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -5423,7 +5423,7 @@ next assert_eq!( server.get_json::("/r/sat/5000000000"), api::SatInscriptions { - ids: vec![], + ids: Vec::new(), page: 0, more: false } From 7c2265adde10078fbeeef3d6365cc3f7c822d1df Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 15 Mar 2024 14:52:40 -0700 Subject: [PATCH 34/49] Vec::new() in tests --- tests/json_api.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/json_api.rs b/tests/json_api.rs index 1d1e37bc92..b41188dfab 100644 --- a/tests/json_api.rs +++ b/tests/json_api.rs @@ -30,7 +30,7 @@ fn get_sat_without_sat_index() { percentile: "100%".into(), satpoint: None, timestamp: 0, - inscriptions: vec![], + inscriptions: Vec::new(), } ) } @@ -389,7 +389,7 @@ fn get_block() { .unwrap(), best_height: 1, height: 0, - inscriptions: vec![], + inscriptions: Vec::new(), } ); } From bd71ec7aa2c7913b9e7af1ac111d90ed1f3d2274 Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 15 Mar 2024 15:05:02 -0700 Subject: [PATCH 35/49] Dedup in parser --- src/index/updater/inscription_updater.rs | 4 +++- src/inscriptions/inscription.rs | 16 +++------------- src/subcommand/wallet/inscribe.rs | 2 +- src/wallet/inscribe/batch_file.rs | 2 +- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 871d106596..3405950c5b 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -260,7 +260,9 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { .. } = flotsam { - purported_parents.retain(|p| potential_parents.contains(p)); + let mut seen = HashSet::new(); + purported_parents + .retain(|parent| seen.insert(*parent) && potential_parents.contains(parent)); } } diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index abce94a214..90519a1b8d 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -168,7 +168,7 @@ impl Inscription { Inscription::append_batch_reveal_script_to_builder(inscriptions, builder).into_script() } - fn inscription_id_field(field: &Option>) -> Option { + fn inscription_id_field(field: Option<&[u8]>) -> Option { let value = field.as_ref()?; if value.len() < Txid::LEN { @@ -236,7 +236,7 @@ impl Inscription { } pub(crate) fn delegate(&self) -> Option { - Self::inscription_id_field(&self.delegate) + Self::inscription_id_field(self.delegate.as_deref()) } pub(crate) fn metadata(&self) -> Option { @@ -247,21 +247,11 @@ impl Inscription { str::from_utf8(self.metaprotocol.as_ref()?).ok() } - /// Returns a deduplicated list of parent inscription IDs the inscription claims to have. pub(crate) fn parents(&self) -> Vec { - let mut unique_parents: HashSet = HashSet::with_capacity(self.parents.len()); self .parents .iter() - .filter_map(|p| { - let Some(parent_id) = Self::inscription_id_field(&Some(p.clone())) else { - return None; - }; - if !unique_parents.insert(parent_id) { - return None; - } - Some(parent_id) - }) + .filter_map(|parent| Self::inscription_id_field(Some(parent))) .collect() } diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index e4bfaa2834..2c181ced94 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -109,7 +109,7 @@ impl Inscribe { self.delegate, metadata, self.metaprotocol, - self.parent.map_or(Vec::new(), |parent| vec![parent]), + self.parent.into_iter().collect(), file, None, )?]; diff --git a/src/wallet/inscribe/batch_file.rs b/src/wallet/inscribe/batch_file.rs index 7c47cd056f..7cfbe4b841 100644 --- a/src/wallet/inscribe/batch_file.rs +++ b/src/wallet/inscribe/batch_file.rs @@ -134,7 +134,7 @@ impl Batchfile { entry.delegate, entry.metadata()?, entry.metaprotocol.clone(), - self.parent.map_or(Vec::new(), |parent| vec![parent]), + self.parent.into_iter().collect(), &entry.file, Some(pointer), )?); From 0dc3615265871f5128012bfe3c821e0eca3d28cc Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 15 Mar 2024 15:14:16 -0700 Subject: [PATCH 36/49] Add InscriptionEntry test --- src/index/entry.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/index/entry.rs b/src/index/entry.rs index d8e7e6c728..d7adb0f314 100644 --- a/src/index/entry.rs +++ b/src/index/entry.rs @@ -192,7 +192,7 @@ impl Entry for RuneId { } } -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq, Clone)] pub(crate) struct InscriptionEntry { pub(crate) charms: u16, pub(crate) fee: u64, @@ -397,6 +397,30 @@ impl Entry for Txid { mod tests { use super::*; + #[test] + fn inscription_entry() { + let id = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefi0" + .parse::() + .unwrap(); + + let entry = InscriptionEntry { + charms: 0, + fee: 1, + height: 2, + id, + inscription_number: 3, + parents: vec![4, 5, 6], + sat: Some(Sat(7)), + sequence_number: 8, + timestamp: 9, + }; + + let value = (0, 1, 2, id.store(), 3, vec![4, 5, 6], Some(7), 8, 9); + + assert_eq!(entry.clone().store(), value); + assert_eq!(InscriptionEntry::load(value), entry); + } + #[test] fn inscription_id_entry() { let inscription_id = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefi0" From c0d5bbd2c415682b3cf311ac76a8741f4d56c274 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 15 Mar 2024 15:22:32 -0700 Subject: [PATCH 37/49] Rename variables in parents_paginated --- src/subcommand/server.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index d4e2312ec0..eb4c7d445a 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1752,24 +1752,24 @@ impl Server { async fn parents_paginated( Extension(server_config): Extension>, Extension(index): Extension>, - Path((child_id, page)): Path<(InscriptionId, usize)>, + Path((id, page)): Path<(InscriptionId, usize)>, ) -> ServerResult { task::block_in_place(|| { - let child_entry = index - .get_inscription_entry(child_id)? - .ok_or_not_found(|| format!("inscription {child_id}"))?; + let child = index + .get_inscription_entry(id)? + .ok_or_not_found(|| format!("inscription {id}"))?; - let (parents, more_parents) = - index.get_parents_by_sequence_number_paginated(child_entry.parents, 100, page)?; + let (parents, more) = + index.get_parents_by_sequence_number_paginated(child.parents, 100, page)?; let prev_page = page.checked_sub(1); - let next_page = more_parents.then_some(page + 1); + let next_page = more.then_some(page + 1); Ok( ParentsHtml { - id: child_id, - number: child_entry.inscription_number, + id, + number: child.inscription_number, parents, prev_page, next_page, From fc98cc4e1390b8afb1a45e8378100853cbd9235e Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 15 Mar 2024 15:24:32 -0700 Subject: [PATCH 38/49] Assert that batch inscription parents are correct by asserting_eq with --- src/wallet/inscribe/batch.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/wallet/inscribe/batch.rs b/src/wallet/inscribe/batch.rs index 310dac5047..6221ac4d40 100644 --- a/src/wallet/inscribe/batch.rs +++ b/src/wallet/inscribe/batch.rs @@ -220,10 +220,9 @@ impl Batch { change: [Address; 2], ) -> Result<(Transaction, Transaction, TweakedKeyPair, u64)> { if let Some(parent_info) = &self.parent_info { - assert!(self - .inscriptions - .iter() - .all(|inscription| { *inscription.parents().first().unwrap() == parent_info.id })) + for inscription in &self.inscriptions { + assert_eq!(inscription.parents(), vec![parent_info.id]); + } } match self.mode { From 30e7a821fd11d0c63631b053697ec7bd5ccf5051 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 15 Mar 2024 15:32:41 -0700 Subject: [PATCH 39/49] In deserialize parent tests, always compare full vec --- src/inscriptions/inscription.rs | 68 +++++++++++++++++---------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index 90519a1b8d..f3cd1edf0a 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -511,13 +511,12 @@ mod tests { ]], ..Default::default() } - .parents() - .first() - .unwrap() - .txid, - "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100" - .parse() - .unwrap() + .parents(), + [ + "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100i0" + .parse() + .unwrap() + ], ); } @@ -528,11 +527,12 @@ mod tests { parents: vec![vec![1; 32]], ..Default::default() } - .parents() - .first() - .unwrap() - .index, - 0 + .parents(), + [ + "0101010101010101010101010101010101010101010101010101010101010101i0" + .parse() + .unwrap() + ], ); } @@ -547,11 +547,12 @@ mod tests { ]], ..Default::default() } - .parents() - .first() - .unwrap() - .index, - 1 + .parents(), + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi1" + .parse() + .unwrap() + ], ); } @@ -566,11 +567,12 @@ mod tests { ]], ..Default::default() } - .parents() - .first() - .unwrap() - .index, - 0x0201, + .parents(), + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi513" + .parse() + .unwrap() + ], ); } @@ -585,11 +587,12 @@ mod tests { ]], ..Default::default() } - .parents() - .first() - .unwrap() - .index, - 0x030201, + .parents(), + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi197121" + .parse() + .unwrap() + ], ); } @@ -604,11 +607,12 @@ mod tests { ]], ..Default::default() } - .parents() - .first() - .unwrap() - .index, - 0x04030201, + .parents(), + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi67305985" + .parse() + .unwrap() + ], ); } From 920a970491dd74b688bd1c757959ff9d4560c5f6 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 15 Mar 2024 15:34:02 -0700 Subject: [PATCH 40/49] Compare with vec --- src/subcommand/wallet/inscribe.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index 2c181ced94..c8066de812 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -1334,22 +1334,16 @@ inscriptions: .unwrap(); assert_eq!( - parent, + vec![parent], ParsedEnvelope::from_transaction(&reveal_tx)[0] .payload - .parents() - .first() - .unwrap() - .clone() + .parents(), ); assert_eq!( - parent, + vec![parent], ParsedEnvelope::from_transaction(&reveal_tx)[1] .payload - .parents() - .first() - .unwrap() - .clone() + .parents(), ); let sig_vbytes = 17; From d6453c31ccba416d4f9bdea87a07d62c479cc6b2 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 15 Mar 2024 15:36:23 -0700 Subject: [PATCH 41/49] Remove -> take --- src/inscriptions/envelope.rs | 14 +++++++------- src/inscriptions/tag.rs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/inscriptions/envelope.rs b/src/inscriptions/envelope.rs index d7d199a049..2f739edbf1 100644 --- a/src/inscriptions/envelope.rs +++ b/src/inscriptions/envelope.rs @@ -48,13 +48,13 @@ impl From for ParsedEnvelope { let duplicate_field = fields.iter().any(|(_key, values)| values.len() > 1); - let content_encoding = Tag::ContentEncoding.remove_field(&mut fields); - let content_type = Tag::ContentType.remove_field(&mut fields); - let delegate = Tag::Delegate.remove_field(&mut fields); - let metadata = Tag::Metadata.remove_field(&mut fields); - let metaprotocol = Tag::Metaprotocol.remove_field(&mut fields); - let parents = Tag::Parent.remove_array_field(&mut fields); - let pointer = Tag::Pointer.remove_field(&mut fields); + let content_encoding = Tag::ContentEncoding.take(&mut fields); + let content_type = Tag::ContentType.take(&mut fields); + let delegate = Tag::Delegate.take(&mut fields); + let metadata = Tag::Metadata.take(&mut fields); + let metaprotocol = Tag::Metaprotocol.take(&mut fields); + let parents = Tag::Parent.take_array(&mut fields); + let pointer = Tag::Pointer.take(&mut fields); let unrecognized_even_field = fields .keys() diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index 4dd7590708..bac0649962 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -88,7 +88,7 @@ impl Tag { mem::swap(&mut tmp, builder); } - pub(crate) fn remove_field(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Option> { + pub(crate) fn take(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Option> { match self.parsing_strategy() { TagCodecStrategy::First => { let values = fields.get_mut(self.bytes())?; @@ -120,7 +120,7 @@ impl Tag { } } - pub(crate) fn remove_array_field(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Vec> { + pub(crate) fn take_array(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Vec> { let values = fields.remove(self.bytes()).unwrap_or_default(); values.into_iter().map(|v| v.to_vec()).collect() } From 7959293b0194edae7c9ff90bed6174c11cfabc58 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 15 Mar 2024 15:38:47 -0700 Subject: [PATCH 42/49] append -> encode --- src/inscriptions/inscription.rs | 14 +++++++------- src/inscriptions/tag.rs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index f3cd1edf0a..32c1efdd7d 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -127,13 +127,13 @@ impl Inscription { .push_opcode(opcodes::all::OP_IF) .push_slice(envelope::PROTOCOL_ID); - Tag::ContentType.encode(&mut builder, &self.content_type); - Tag::ContentEncoding.encode(&mut builder, &self.content_encoding); - Tag::Metaprotocol.encode(&mut builder, &self.metaprotocol); - Tag::Parent.encode_array(&mut builder, &self.parents); - Tag::Delegate.encode(&mut builder, &self.delegate); - Tag::Pointer.encode(&mut builder, &self.pointer); - Tag::Metadata.encode(&mut builder, &self.metadata); + Tag::ContentEncoding.append(&mut builder, &self.content_encoding); + Tag::ContentType.append(&mut builder, &self.content_type); + Tag::Delegate.append(&mut builder, &self.delegate); + Tag::Metadata.append(&mut builder, &self.metadata); + Tag::Metaprotocol.append(&mut builder, &self.metaprotocol); + Tag::Parent.append_array(&mut builder, &self.parents); + Tag::Pointer.append(&mut builder, &self.pointer); if let Some(body) = &self.body { builder = builder.push_slice(envelope::BODY_TAG); diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index bac0649962..74c1793f1a 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -49,7 +49,7 @@ impl Tag { } } - pub(crate) fn encode(self, builder: &mut script::Builder, value: &Option>) { + pub(crate) fn append(self, builder: &mut script::Builder, value: &Option>) { if let Some(value) = value { let mut tmp = script::Builder::new(); mem::swap(&mut tmp, builder); @@ -75,7 +75,7 @@ impl Tag { } } - pub(crate) fn encode_array(self, builder: &mut script::Builder, values: &Vec>) { + pub(crate) fn append_array(self, builder: &mut script::Builder, values: &Vec>) { let mut tmp = script::Builder::new(); mem::swap(&mut tmp, builder); From 872290d1730b7f7059d4756b5a52537b1d4f8005 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 15 Mar 2024 15:41:52 -0700 Subject: [PATCH 43/49] Remove comments --- src/index.rs | 2 -- src/inscriptions/tag.rs | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/index.rs b/src/index.rs index 23957f92c2..b7b9b9e8d0 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1088,8 +1088,6 @@ impl Index { .open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY) .unwrap(); - // we need to introduce a temporary variable to appease the borrow checker - // a historically tried and true strategy let parent_sequences = InscriptionEntry::load( sequence_number_to_inscription_entry .get(sequence_number) diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index 74c1793f1a..8847f9d7fe 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -66,9 +66,7 @@ impl Tag { .push_slice::<&script::PushBytes>(self.bytes().try_into().unwrap()) .push_slice::<&script::PushBytes>(chunk.try_into().unwrap()); } - } // TagParsingStrategy::Array => { - // unimplemented!() - // } + } } mem::swap(&mut tmp, builder); From 9f78f3c1ee05bea0d108bd09c8eb7c2157f0e56c Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 15 Mar 2024 15:50:22 -0700 Subject: [PATCH 44/49] Simplify take_array --- src/inscriptions/tag.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index 8847f9d7fe..12f9da132e 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -119,7 +119,11 @@ impl Tag { } pub(crate) fn take_array(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Vec> { - let values = fields.remove(self.bytes()).unwrap_or_default(); - values.into_iter().map(|v| v.to_vec()).collect() + fields + .remove(self.bytes()) + .unwrap_or_default() + .into_iter() + .map(|v| v.to_vec()) + .collect() } } From e23a2aa34e861e61b48ea96c8921972776a3abca Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 15 Mar 2024 15:55:31 -0700 Subject: [PATCH 45/49] Get rid of tag codec strategy --- src/inscriptions/tag.rs | 69 +++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 44 deletions(-) diff --git a/src/inscriptions/tag.rs b/src/inscriptions/tag.rs index 12f9da132e..34602c50cb 100644 --- a/src/inscriptions/tag.rs +++ b/src/inscriptions/tag.rs @@ -18,19 +18,9 @@ pub(crate) enum Tag { Nop, } -enum TagCodecStrategy { - First, - Chunked, - Array, -} - impl Tag { - fn parsing_strategy(self) -> TagCodecStrategy { - match self { - Tag::Metadata => TagCodecStrategy::Chunked, - Tag::Parent => TagCodecStrategy::Array, - _ => TagCodecStrategy::First, - } + fn chunked(self) -> bool { + matches!(self, Self::Metadata) } pub(crate) fn bytes(self) -> &'static [u8] { @@ -54,19 +44,16 @@ impl Tag { let mut tmp = script::Builder::new(); mem::swap(&mut tmp, builder); - match self.parsing_strategy() { - TagCodecStrategy::First | TagCodecStrategy::Array => { + if self.chunked() { + for chunk in value.chunks(MAX_SCRIPT_ELEMENT_SIZE) { tmp = tmp .push_slice::<&script::PushBytes>(self.bytes().try_into().unwrap()) - .push_slice::<&script::PushBytes>(value.as_slice().try_into().unwrap()); - } - TagCodecStrategy::Chunked => { - for chunk in value.chunks(MAX_SCRIPT_ELEMENT_SIZE) { - tmp = tmp - .push_slice::<&script::PushBytes>(self.bytes().try_into().unwrap()) - .push_slice::<&script::PushBytes>(chunk.try_into().unwrap()); - } + .push_slice::<&script::PushBytes>(chunk.try_into().unwrap()); } + } else { + tmp = tmp + .push_slice::<&script::PushBytes>(self.bytes().try_into().unwrap()) + .push_slice::<&script::PushBytes>(value.as_slice().try_into().unwrap()); } mem::swap(&mut tmp, builder); @@ -87,33 +74,27 @@ impl Tag { } pub(crate) fn take(self, fields: &mut BTreeMap<&[u8], Vec<&[u8]>>) -> Option> { - match self.parsing_strategy() { - TagCodecStrategy::First => { - let values = fields.get_mut(self.bytes())?; + if self.chunked() { + let value = fields.remove(self.bytes())?; - if values.is_empty() { - None - } else { - let value = values.remove(0).to_vec(); + if value.is_empty() { + None + } else { + Some(value.into_iter().flatten().cloned().collect()) + } + } else { + let values = fields.get_mut(self.bytes())?; - if values.is_empty() { - fields.remove(self.bytes()); - } + if values.is_empty() { + None + } else { + let value = values.remove(0).to_vec(); - Some(value) + if values.is_empty() { + fields.remove(self.bytes()); } - } - TagCodecStrategy::Chunked => { - let value = fields.remove(self.bytes())?; - if value.is_empty() { - None - } else { - Some(value.into_iter().flatten().cloned().collect()) - } - } - TagCodecStrategy::Array => { - panic!("Array-type fields must not be removed as a simple byte array.") + Some(value) } } } From 6be2e4f8cca59467da0728b7ff3437cc2828bf30 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 15 Mar 2024 16:00:24 -0700 Subject: [PATCH 46/49] Use iterator --- src/index/updater/inscription_updater.rs | 30 +++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/index/updater/inscription_updater.rs b/src/index/updater/inscription_updater.rs index 3405950c5b..4dd5be7e82 100644 --- a/src/index/updater/inscription_updater.rs +++ b/src/index/updater/inscription_updater.rs @@ -497,20 +497,22 @@ impl<'a, 'db, 'tx> InscriptionUpdater<'a, 'db, 'tx> { self.sat_to_sequence_number.insert(&n, &sequence_number)?; } - let mut parent_sequence_numbers = Vec::new(); - for parent_id in &parents { - let parent_sequence_number = self - .id_to_sequence_number - .get(&parent_id.store())? - .unwrap() - .value(); - - self - .sequence_number_to_children - .insert(parent_sequence_number, sequence_number)?; - - parent_sequence_numbers.push(parent_sequence_number); - } + let parent_sequence_numbers = parents + .iter() + .map(|parent| { + let parent_sequence_number = self + .id_to_sequence_number + .get(&parent.store())? + .unwrap() + .value(); + + self + .sequence_number_to_children + .insert(parent_sequence_number, sequence_number)?; + + Ok(parent_sequence_number) + }) + .collect::>>()?; if let Some(sender) = self.event_sender { sender.blocking_send(Event::InscriptionCreated { From afc533dbb6528a6d29be17071dbc3b17bf831acc Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 15 Mar 2024 16:01:21 -0700 Subject: [PATCH 47/49] Paginate parents --- src/index.rs | 34 +++++----- src/subcommand/server.rs | 136 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 154 insertions(+), 16 deletions(-) diff --git a/src/index.rs b/src/index.rs index 23957f92c2..4c36960dcc 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1152,17 +1152,17 @@ impl Index { pub(crate) fn get_parents_by_sequence_number_paginated( &self, parent_sequence_numbers: Vec, - page_size: usize, page_index: usize, ) -> Result<(Vec, bool)> { + const PAGE_SIZE: usize = 100; let rtx = self.database.begin_read()?; let sequence_number_to_entry = rtx.open_table(SEQUENCE_NUMBER_TO_INSCRIPTION_ENTRY)?; let mut parents = parent_sequence_numbers .iter() - .skip(page_index * page_size) - .take(page_size.saturating_add(1)) + .skip(page_index * PAGE_SIZE) + .take(PAGE_SIZE.saturating_add(1)) .map(|sequence_number| { sequence_number_to_entry .get(sequence_number) @@ -1171,7 +1171,7 @@ impl Index { }) .collect::>>()?; - let more_parents = parents.len() > 100; + let more_parents = parents.len() > PAGE_SIZE; if more_parents { parents.pop(); @@ -1848,18 +1848,22 @@ impl Index { None }; - let mut parents = Vec::new(); - for parent in entry.parents.iter() { - parents.push( - InscriptionEntry::load( - sequence_number_to_inscription_entry - .get(parent)? - .unwrap() - .value(), + let parents = entry + .parents + .iter() + .take(4) + .map(|parent| { + Ok( + InscriptionEntry::load( + sequence_number_to_inscription_entry + .get(parent)? + .unwrap() + .value(), + ) + .id, ) - .id, - ); - } + }) + .collect::>>()?; let mut charms = entry.charms; diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index d4e2312ec0..9f95ca74c7 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -217,6 +217,10 @@ impl Server { .route("/ordinal/:sat", get(Self::ordinal)) .route("/output/:output", get(Self::output)) .route("/parents/:inscription_id", get(Self::parents)) + .route( + "/parents/:inscription_id/:page", + get(Self::parents_paginated), + ) .route("/preview/:inscription_id", get(Self::preview)) .route("/r/blockhash", get(Self::block_hash_json)) .route( @@ -1760,7 +1764,7 @@ impl Server { .ok_or_not_found(|| format!("inscription {child_id}"))?; let (parents, more_parents) = - index.get_parents_by_sequence_number_paginated(child_entry.parents, 100, page)?; + index.get_parents_by_sequence_number_paginated(child_entry.parents, page)?; let prev_page = page.checked_sub(1); @@ -4902,6 +4906,136 @@ next ); } + #[test] + fn inscription_with_parent_page() { + let server = TestServer::builder().chain(Chain::Regtest).build(); + server.mine_blocks(2); + + let parent_a_txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..Default::default() + }); + + let parent_b_txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, inscription("text/plain", "hello").to_witness())], + ..Default::default() + }); + + server.mine_blocks(1); + + let parent_a_inscription_id = InscriptionId { + txid: parent_a_txid, + index: 0, + }; + + let parent_b_inscription_id = InscriptionId { + txid: parent_b_txid, + index: 0, + }; + + let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[ + ( + 3, + 0, + 0, + Inscription { + content_type: Some("text/plain".into()), + body: Some("hello".into()), + parents: vec![ + parent_a_inscription_id.value(), + parent_b_inscription_id.value(), + ], + ..Default::default() + } + .to_witness(), + ), + (3, 1, 0, Default::default()), + (3, 2, 0, Default::default()), + ], + ..Default::default() + }); + + server.mine_blocks(1); + + let inscription_id = InscriptionId { txid, index: 0 }; + + server.assert_response_regex( + format!("/parents/{inscription_id}"), + StatusCode::OK, + format!(".*Inscription -1 Parents.*

Inscription -1 Parents

.*
.*.*"), + ); + } + + #[test] + fn inscription_parent_page_pagination() { + let server = TestServer::builder().chain(Chain::Regtest).build(); + + server.mine_blocks(1); + + let mut parent_ids = Vec::new(); + let mut inputs = Vec::new(); + for i in 0..101 { + parent_ids.push( + InscriptionId { + txid: server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(i + 1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..Default::default() + }), + index: 0, + } + .value(), + ); + + inputs.push((i + 2, 1, 0, Witness::default())); + + server.mine_blocks(1); + } + + inputs.insert( + 0, + ( + 101, + 0, + 0, + Inscription { + content_type: Some("text/plain".into()), + body: Some("hello".into()), + parents: parent_ids, + ..Default::default() + } + .to_witness(), + ), + ); + + let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &inputs, + ..Default::default() + }); + + server.mine_blocks(1); + + let inscription_id = InscriptionId { txid, index: 0 }; + + server.assert_response_regex( + format!("/parents/{inscription_id}"), + StatusCode::OK, + format!(".*Inscription -1 Parents.*

Inscription -1 Parents

.*
(.*.*){{100}}.*"), + ); + + server.assert_response_regex( + format!("/parents/{inscription_id}/1"), + StatusCode::OK, + format!(".*Inscription -1 Parents.*

Inscription -1 Parents

.*
(.*.*){{1}}.*"), + ); + + server.assert_response_regex( + format!("/inscription/{inscription_id}"), + StatusCode::OK, + format!(".*Inscription -1.*

Inscription -1

.*
(.*.*){{4}}.*"), + ); + } + #[test] fn inscription_number_endpoint() { let server = TestServer::builder().chain(Chain::Regtest).build(); From 6d8349cdd864f23455076a651ac529c403726dff Mon Sep 17 00:00:00 2001 From: raphjaph Date: Fri, 15 Mar 2024 16:17:20 -0700 Subject: [PATCH 48/49] Amend --- src/inscriptions/inscription.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index 32c1efdd7d..4d0b753c99 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -616,6 +616,36 @@ mod tests { ); } + #[test] + fn inscription_parent_returns_multiple_parents() { + assert_eq!( + Inscription { + parents: vec![ + vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x01, 0x02, 0x03, 0x04, + ], + vec![ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x02, 0x03, 0x04, + ] + ], + ..Default::default() + } + .parents(), + [ + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi67305985" + .parse() + .unwrap(), + "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffi67305984" + .parse() + .unwrap() + ], + ); + } + #[test] fn metadata_function_decodes_metadata() { assert_eq!( From acc89bf90ca3dc0dde559c262717d97386dc17c1 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Fri, 15 Mar 2024 16:20:07 -0700 Subject: [PATCH 49/49] Tweak --- src/inscriptions/inscription.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index 4d0b753c99..39179693a8 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -127,13 +127,13 @@ impl Inscription { .push_opcode(opcodes::all::OP_IF) .push_slice(envelope::PROTOCOL_ID); - Tag::ContentEncoding.append(&mut builder, &self.content_encoding); Tag::ContentType.append(&mut builder, &self.content_type); - Tag::Delegate.append(&mut builder, &self.delegate); - Tag::Metadata.append(&mut builder, &self.metadata); + Tag::ContentEncoding.append(&mut builder, &self.content_encoding); Tag::Metaprotocol.append(&mut builder, &self.metaprotocol); Tag::Parent.append_array(&mut builder, &self.parents); + Tag::Delegate.append(&mut builder, &self.delegate); Tag::Pointer.append(&mut builder, &self.pointer); + Tag::Metadata.append(&mut builder, &self.metadata); if let Some(body) = &self.body { builder = builder.push_slice(envelope::BODY_TAG);