diff --git a/src/index.rs b/src/index.rs index 093404a75a..039bae32e3 100644 --- a/src/index.rs +++ b/src/index.rs @@ -5081,6 +5081,67 @@ mod tests { } } + #[test] + fn inscription_with_three_delegates_serves_the_first_available_one() { + for context in Context::configurations() { + context.mine_blocks(1); + + let delegate_txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0, inscription("text/plain", "hello").to_witness())], + ..Default::default() + }); + + context.mine_blocks(1); + + let delegate_inscription_id_real = InscriptionId { + txid: delegate_txid, + index: 0, + }; + + let delegate_inscription_id_fake_a = InscriptionId { + txid: delegate_txid, + index: 1, + }; + let delegate_inscription_id_fake_b = InscriptionId { + txid: delegate_txid, + index: 2, + }; + + let multi_delegate_inscription = Inscription { + content_type: Some("text/plain".into()), + body: Some("world".into()), + delegates: vec![ + delegate_inscription_id_fake_a.value(), + delegate_inscription_id_real.value(), + delegate_inscription_id_fake_b.value(), + ], + ..Default::default() + }; + let txid = context.rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(2, 0, 0, multi_delegate_inscription.to_witness())], + ..Default::default() + }); + + context.mine_blocks(1); + let inscription_id = InscriptionId { txid, index: 0 }; + + let recovered_delegator = context + .index + .get_inscription_by_id(inscription_id) + .unwrap() + .unwrap(); + + assert_eq!( + recovered_delegator.delegates(), + vec![ + delegate_inscription_id_fake_a, + delegate_inscription_id_real, + delegate_inscription_id_fake_b + ] + ); + } + } + #[test] fn inscription_with_pointer() { for context in Context::configurations() { diff --git a/src/inscriptions/envelope.rs b/src/inscriptions/envelope.rs index da54535ec6..6004e34ddd 100644 --- a/src/inscriptions/envelope.rs +++ b/src/inscriptions/envelope.rs @@ -50,7 +50,7 @@ impl From for ParsedEnvelope { let content_encoding = Tag::ContentEncoding.take(&mut fields); let content_type = Tag::ContentType.take(&mut fields); - let delegate = Tag::Delegate.take(&mut fields); + let delegates = Tag::Delegate.take_array(&mut fields); let metadata = Tag::Metadata.take(&mut fields); let metaprotocol = Tag::Metaprotocol.take(&mut fields); let parents = Tag::Parent.take_array(&mut fields); @@ -72,7 +72,7 @@ impl From for ParsedEnvelope { }), content_encoding, content_type, - delegate, + delegates, duplicate_field, incomplete_field, metadata, diff --git a/src/inscriptions/inscription.rs b/src/inscriptions/inscription.rs index f764f5ed96..6b53b1db1b 100644 --- a/src/inscriptions/inscription.rs +++ b/src/inscriptions/inscription.rs @@ -16,7 +16,7 @@ pub struct Inscription { pub body: Option>, pub content_encoding: Option>, pub content_type: Option>, - pub delegate: Option>, + pub delegates: Vec>, pub duplicate_field: bool, pub incomplete_field: bool, pub metadata: Option>, @@ -40,7 +40,7 @@ impl Inscription { pub(crate) fn from_file( chain: Chain, compress: bool, - delegate: Option, + delegates: Vec, metadata: Option>, metaprotocol: Option, parents: Vec, @@ -101,7 +101,7 @@ impl Inscription { body: Some(body), content_encoding, content_type: Some(content_type.into()), - delegate: delegate.map(|delegate| delegate.value()), + delegates: delegates.iter().map(|delegate| delegate.value()).collect(), metadata, metaprotocol: metaprotocol.map(|metaprotocol| metaprotocol.into_bytes()), parents: parents.iter().map(|parent| parent.value()).collect(), @@ -134,7 +134,7 @@ impl Inscription { 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::Delegate.append_array(&mut builder, &self.delegates); Tag::Pointer.append(&mut builder, &self.pointer); Tag::Metadata.append(&mut builder, &self.metadata); Tag::Rune.append(&mut builder, &self.rune); @@ -239,8 +239,12 @@ impl Inscription { HeaderValue::from_str(str::from_utf8(self.content_encoding.as_ref()?).unwrap_or_default()).ok() } - pub(crate) fn delegate(&self) -> Option { - Self::inscription_id_field(self.delegate.as_deref()) + pub(crate) fn delegates(&self) -> Vec { + self + .delegates + .iter() + .filter_map(|delegate| Self::inscription_id_field(Some(delegate))) + .collect() } pub(crate) fn metadata(&self) -> Option { @@ -488,14 +492,15 @@ mod tests { fn inscription_delegate_txid_is_deserialized_correctly() { assert_eq!( Inscription { - delegate: Some(vec![ + delegates: 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() } - .delegate() + .delegates() + .first() .unwrap() .txid, "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100" @@ -777,7 +782,7 @@ mod tests { let inscription = Inscription::from_file( Chain::Mainnet, false, - None, + Vec::new(), None, None, Vec::new(), @@ -792,7 +797,7 @@ mod tests { let inscription = Inscription::from_file( Chain::Mainnet, false, - None, + Vec::new(), None, None, Vec::new(), @@ -807,7 +812,7 @@ mod tests { let inscription = Inscription::from_file( Chain::Mainnet, false, - None, + Vec::new(), None, None, Vec::new(), @@ -822,7 +827,7 @@ mod tests { let inscription = Inscription::from_file( Chain::Mainnet, false, - None, + Vec::new(), None, None, Vec::new(), diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index c5963bf0e1..7bae9a0180 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1341,11 +1341,14 @@ impl Server { }; }; - if let Some(delegate) = inscription.delegate() { - inscription = index - .get_inscription_by_id(delegate)? - .ok_or_not_found(|| format!("delegate {inscription_id}"))? - } + // return the first inscription where the delegate exists + // probably worth considering some sort of limitation to mitigate "DoSability" + let inscription_override = inscription + .delegates() + .iter() + .find_map(|delegate| index.get_inscription_by_id(*delegate).unwrap_or(None)); + + inscription = inscription_override.unwrap_or(inscription); Ok( Self::content_response(inscription, accept_encoding, &server_config)? @@ -1441,11 +1444,12 @@ impl Server { .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; - if let Some(delegate) = inscription.delegate() { - inscription = index - .get_inscription_by_id(delegate)? - .ok_or_not_found(|| format!("delegate {inscription_id}"))? - } + let inscription_override = inscription + .delegates() + .iter() + .find_map(|delegate| index.get_inscription_by_id(*delegate).unwrap_or(None)); + + inscription = inscription_override.unwrap_or(inscription); let media = inscription.media(); @@ -5893,7 +5897,7 @@ next server.mine_blocks(1); let inscription = Inscription { - delegate: Some(delegate.value()), + delegates: vec![delegate.value()], ..Default::default() }; diff --git a/src/subcommand/wallet/inscribe.rs b/src/subcommand/wallet/inscribe.rs index d81efae624..2588e9d666 100644 --- a/src/subcommand/wallet/inscribe.rs +++ b/src/subcommand/wallet/inscribe.rs @@ -107,7 +107,7 @@ impl Inscribe { inscriptions = vec![Inscription::from_file( chain, self.compress, - self.delegate, + self.delegate.into_iter().collect(), metadata, self.metaprotocol, self.parent.into_iter().collect(), diff --git a/src/wallet/inscribe/batchfile.rs b/src/wallet/inscribe/batchfile.rs index 7f2dc7d490..191c62479c 100644 --- a/src/wallet/inscribe/batchfile.rs +++ b/src/wallet/inscribe/batchfile.rs @@ -132,7 +132,7 @@ impl Batchfile { inscriptions.push(Inscription::from_file( wallet.chain(), compress, - entry.delegate, + entry.delegate.into_iter().collect(), entry.metadata()?, entry.metaprotocol.clone(), self.parent.into_iter().collect(), diff --git a/templates/inscription.html b/templates/inscription.html index 2e54991985..af38547055 100644 --- a/templates/inscription.html +++ b/templates/inscription.html @@ -73,8 +73,8 @@

Inscription {{ self.number }}

metaprotocol
{{ metaprotocol }}
%% } -%% if self.inscription.content_length().is_some() || self.inscription.delegate().is_some() { -%% if let Some(delegate) = self.inscription.delegate() { +%% if self.inscription.content_length().is_some() || self.inscription.delegates().first().is_some() { +%% if let Some(delegate) = self.inscription.delegates().first() {
delegate
{{ delegate }}
%% }