From 6c1c658b573a1139c90a8ee4cb2b77075ccb1b4a Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Sat, 16 Mar 2024 11:28:27 -0700 Subject: [PATCH 1/4] Allow multiple delegates. --- src/inscriptions/envelope.rs | 4 ++-- src/inscriptions/inscription.rs | 31 ++++++++++++++++++------------- src/subcommand/server.rs | 10 +++++----- src/subcommand/wallet/inscribe.rs | 2 +- src/wallet/inscribe/batchfile.rs | 2 +- templates/inscription.html | 4 ++-- 6 files changed, 29 insertions(+), 24 deletions(-) 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..afbba6e75b 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1341,9 +1341,9 @@ impl Server { }; }; - if let Some(delegate) = inscription.delegate() { + if let Some(delegate) = inscription.delegates().first() { inscription = index - .get_inscription_by_id(delegate)? + .get_inscription_by_id(delegate.clone())? .ok_or_not_found(|| format!("delegate {inscription_id}"))? } @@ -1441,9 +1441,9 @@ impl Server { .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; - if let Some(delegate) = inscription.delegate() { + if let Some(delegate) = inscription.delegates().first() { inscription = index - .get_inscription_by_id(delegate)? + .get_inscription_by_id(delegate.clone())? .ok_or_not_found(|| format!("delegate {inscription_id}"))? } @@ -5893,7 +5893,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 }}
%% } From 3e47381b158fb35abe9b1ac62665bd90dcfbfaa7 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Sat, 16 Mar 2024 20:46:58 -0700 Subject: [PATCH 2/4] Serve the first found delegate for an inscription. --- src/subcommand/server.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index afbba6e75b..14a537d002 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1341,11 +1341,15 @@ impl Server { }; }; - if let Some(delegate) = inscription.delegates().first() { - inscription = index - .get_inscription_by_id(delegate.clone())? - .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.clone()) + .unwrap_or(None) + }); + + inscription = inscription_override.unwrap_or(inscription); Ok( Self::content_response(inscription, accept_encoding, &server_config)? @@ -1441,11 +1445,13 @@ impl Server { .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; - if let Some(delegate) = inscription.delegates().first() { - inscription = index - .get_inscription_by_id(delegate.clone())? - .ok_or_not_found(|| format!("delegate {inscription_id}"))? - } + let inscription_override = inscription.delegates().iter().find_map(|delegate| { + index + .get_inscription_by_id(delegate.clone()) + .unwrap_or(None) + }); + + inscription = inscription_override.unwrap_or(inscription); let media = inscription.media(); From 5c96e53551c0bd7e8540d249de15dcf0f9fd13b4 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Sat, 16 Mar 2024 21:07:35 -0700 Subject: [PATCH 3/4] Create multi-inscription serialization test. --- src/index.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) 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() { From 5d399ca4c20eb3a64a78c94eb09a0f280417a0b1 Mon Sep 17 00:00:00 2001 From: Arik Sosman Date: Sat, 16 Mar 2024 21:12:16 -0700 Subject: [PATCH 4/4] Fix .clone() --- src/subcommand/server.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 14a537d002..7bae9a0180 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -1343,11 +1343,10 @@ impl Server { // 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.clone()) - .unwrap_or(None) - }); + let inscription_override = inscription + .delegates() + .iter() + .find_map(|delegate| index.get_inscription_by_id(*delegate).unwrap_or(None)); inscription = inscription_override.unwrap_or(inscription); @@ -1445,11 +1444,10 @@ impl Server { .get_inscription_by_id(inscription_id)? .ok_or_not_found(|| format!("inscription {inscription_id}"))?; - let inscription_override = inscription.delegates().iter().find_map(|delegate| { - index - .get_inscription_by_id(delegate.clone()) - .unwrap_or(None) - }); + let inscription_override = inscription + .delegates() + .iter() + .find_map(|delegate| index.get_inscription_by_id(*delegate).unwrap_or(None)); inscription = inscription_override.unwrap_or(inscription);