Skip to content

Commit

Permalink
Make inscription type more flexible (ordinals#892)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Dec 6, 2022
1 parent fd940b7 commit fff7312
Show file tree
Hide file tree
Showing 18 changed files with 126 additions and 102 deletions.
5 changes: 5 additions & 0 deletions src/content.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[derive(Debug, PartialEq)]
pub(crate) enum Content<'a> {
Text(&'a str),
Png(&'a [u8]),
}
126 changes: 63 additions & 63 deletions src/inscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,18 @@ use {
util::taproot::TAPROOT_ANNEX_PREFIX,
Script, Witness,
},
std::{
iter::Peekable,
str::{self, Utf8Error},
},
std::{iter::Peekable, str},
};

const PROTOCOL_ID: &[u8] = b"ord";
const RESOURCE_TAG: &[u8] = &[];
const TYPE_TAG: &[u8] = &[1];

const CONTENT_TAG: &[u8] = &[];
const CONTENT_TYPE_TAG: &[u8] = &[1];

#[derive(Debug, PartialEq)]
pub(crate) enum Inscription {
Text(Vec<u8>),
Png(Vec<u8>),
pub(crate) struct Inscription {
pub(crate) content: Vec<u8>,
pub(crate) content_type: Vec<u8>,
}

impl Inscription {
Expand All @@ -30,50 +28,52 @@ impl Inscription {
}

pub(crate) fn from_file(path: PathBuf) -> Result<Self, Error> {
let file = fs::read(&path).with_context(|| format!("io error reading {}", path.display()))?;
let content =
fs::read(&path).with_context(|| format!("io error reading {}", path.display()))?;

if file.len() > 520 {
if content.len() > 520 {
bail!("file size exceeds 520 bytes");
}

match path
let content_type = match path
.extension()
.ok_or_else(|| anyhow!("file must have extension"))?
.to_str()
.ok_or_else(|| anyhow!("unrecognized extension"))?
{
"txt" => Ok(Inscription::Text(file)),
"png" => Ok(Inscription::Png(file)),
other => Err(anyhow!(
"unrecognized file extension `.{other}`, only .txt and .png accepted"
)),
}
"txt" => "text/plain;charset=utf-8",
"png" => "image/png",
other => {
return Err(anyhow!(
"unrecognized file extension `.{other}`, only .txt and .png accepted"
))
}
};

Ok(Self {
content,
content_type: content_type.into(),
})
}

pub(crate) fn append_reveal_script(&self, builder: script::Builder) -> Script {
builder
.push_opcode(opcodes::OP_FALSE)
.push_opcode(opcodes::all::OP_IF)
.push_slice(PROTOCOL_ID)
.push_slice(TYPE_TAG)
.push_slice(self.media_type().as_bytes())
.push_slice(RESOURCE_TAG)
.push_slice(self.resource())
.push_slice(CONTENT_TYPE_TAG)
.push_slice(&self.content_type)
.push_slice(CONTENT_TAG)
.push_slice(&self.content)
.push_opcode(opcodes::all::OP_ENDIF)
.into_script()
}

fn media_type(&self) -> &str {
match self {
Inscription::Text(_) => "text/plain;charset=utf-8",
Inscription::Png(_) => "image/png",
}
}

fn resource(&self) -> &[u8] {
match self {
Inscription::Text(text) => text.as_ref(),
Inscription::Png(png) => png.as_ref(),
pub(crate) fn content(&self) -> Option<Content> {
match self.content_type.as_slice() {
b"text/plain;charset=utf-8" => Some(Content::Text(str::from_utf8(&self.content).ok()?)),
b"image/png" => Some(Content::Png(&self.content)),
_ => None,
}
}
}
Expand All @@ -84,7 +84,6 @@ enum InscriptionError {
KeyPathSpend,
Script(script::Error),
NoInscription,
Utf8Decode(Utf8Error),
InvalidInscription,
}

Expand Down Expand Up @@ -154,17 +153,13 @@ impl<'a> InscriptionParser<'a> {
return Err(InscriptionError::NoInscription);
}

if !self.accept(Instruction::PushBytes(TYPE_TAG))? {
if !self.accept(Instruction::PushBytes(CONTENT_TYPE_TAG))? {
return Err(InscriptionError::InvalidInscription);
}

let media_type = if let Instruction::PushBytes(bytes) = self.advance()? {
str::from_utf8(bytes).map_err(InscriptionError::Utf8Decode)?
} else {
return Err(InscriptionError::InvalidInscription);
};
let content_type = self.expect_push()?;

if !self.accept(Instruction::PushBytes(RESOURCE_TAG))? {
if !self.accept(Instruction::PushBytes(CONTENT_TAG))? {
return Err(InscriptionError::InvalidInscription);
}

Expand All @@ -173,19 +168,16 @@ impl<'a> InscriptionParser<'a> {
content.extend_from_slice(self.expect_push()?);
}

let inscription = match media_type {
"text/plain;charset=utf-8" => Some(Inscription::Text(content)),
"image/png" => Some(Inscription::Png(content)),
_ => None,
};

return Ok(inscription);
return Ok(Some(Inscription {
content,
content_type: content_type.into(),
}));
}

Ok(None)
}

fn expect_push(&mut self) -> Result<&[u8]> {
fn expect_push(&mut self) -> Result<&'a [u8]> {
match self.advance()? {
Instruction::PushBytes(bytes) => Ok(bytes),
_ => Err(InscriptionError::InvalidInscription),
Expand Down Expand Up @@ -276,12 +268,12 @@ mod tests {
&[],
b"ord",
])),
Ok(Inscription::Text("ord".into()))
Ok(inscription("text/plain;charset=utf-8", "ord")),
);
}

#[test]
fn valid_resource_in_multiple_pushes() {
fn valid_content_in_multiple_pushes() {
assert_eq!(
InscriptionParser::parse(&container(&[
b"ord",
Expand All @@ -291,20 +283,20 @@ mod tests {
b"foo",
b"bar"
])),
Ok(Inscription::Text("foobar".into()))
Ok(inscription("text/plain;charset=utf-8", "foobar")),
);
}

#[test]
fn valid_resource_in_zero_pushes() {
fn valid_content_in_zero_pushes() {
assert_eq!(
InscriptionParser::parse(&container(&[
b"ord",
&[1],
b"text/plain;charset=utf-8",
&[]
])),
Ok(Inscription::Text("".into()))
Ok(inscription("text/plain;charset=utf-8", "")),
);
}

Expand All @@ -324,7 +316,7 @@ mod tests {

assert_eq!(
InscriptionParser::parse(&Witness::from_vec(vec![script.into_bytes(), vec![]])),
Ok(Inscription::Text("ord".into()))
Ok(inscription("text/plain;charset=utf-8", "ord")),
);
}

Expand All @@ -344,7 +336,7 @@ mod tests {

assert_eq!(
InscriptionParser::parse(&Witness::from_vec(vec![script.into_bytes(), vec![]])),
Ok(Inscription::Text("ord".into()))
Ok(inscription("text/plain;charset=utf-8", "ord")),
);
}

Expand All @@ -371,22 +363,30 @@ mod tests {

assert_eq!(
InscriptionParser::parse(&Witness::from_vec(vec![script.into_bytes(), vec![]])),
Ok(Inscription::Text("foo".into()))
Ok(inscription("text/plain;charset=utf-8", "foo")),
);
}

#[test]
fn invalid_utf8_is_allowed() {
assert!(matches!(
fn invalid_utf8_does_not_render_inscription_invalid() {
assert_eq!(
InscriptionParser::parse(&container(&[
b"ord",
&[1],
b"text/plain;charset=utf-8",
&[],
&[0b10000000]
])),
Ok(Inscription::Text(bytes)) if bytes == [0b10000000],
));
Ok(inscription("text/plain;charset=utf-8", [0b10000000])),
);
}

#[test]
fn invalid_utf8_has_no_content() {
assert_eq!(
inscription("text/plain;charset=utf-8", [0b10000000]).content(),
None,
);
}

#[test]
Expand Down Expand Up @@ -451,7 +451,7 @@ mod tests {

assert_eq!(
Inscription::from_transaction(&tx),
Some(Inscription::Text("ord".into())),
Some(inscription("text/plain;charset=utf-8", "ord")),
);
}

Expand Down Expand Up @@ -515,7 +515,7 @@ mod tests {
fn inscribe_png() {
assert_eq!(
InscriptionParser::parse(&container(&[b"ord", &[1], b"image/png", &[], &[1; 100]])),
Ok(Inscription::Png(vec![1; 100]))
Ok(inscription("image/png", [1; 100])),
);
}
}
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use {
self::{
arguments::Arguments,
blocktime::Blocktime,
content::Content,
decimal::Decimal,
degree::Degree,
epoch::Epoch,
Expand Down Expand Up @@ -68,6 +69,7 @@ use self::test::*;
mod arguments;
mod blocktime;
mod chain;
mod content;
mod decimal;
mod degree;
mod epoch;
Expand Down
6 changes: 3 additions & 3 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use {
self::{
deserialize_from_str::DeserializeFromStr,
templates::{
BlockHtml, ClockSvg, Content, HomeHtml, InputHtml, InscriptionHtml, OrdinalHtml, OutputHtml,
PageHtml, RangeHtml, RareTxt, TransactionHtml,
BlockHtml, ClockSvg, HomeHtml, InputHtml, InscriptionHtml, OrdinalHtml, OutputHtml,
PageContent, PageHtml, RangeHtml, RareTxt, TransactionHtml,
},
},
axum::{
Expand Down Expand Up @@ -91,7 +91,7 @@ struct StaticHtml {
html: &'static str,
}

impl Content for StaticHtml {
impl PageContent for StaticHtml {
fn title(&self) -> String {
self.title.into()
}
Expand Down
12 changes: 6 additions & 6 deletions src/subcommand/server/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ mod transaction;
#[derive(Boilerplate)]
pub(crate) struct PageHtml {
chain: Chain,
content: Box<dyn Content>,
content: Box<dyn PageContent>,
has_ordinal_index: bool,
}

impl PageHtml {
pub(crate) fn new<T: Content + 'static>(
pub(crate) fn new<T: PageContent + 'static>(
content: T,
chain: Chain,
has_ordinal_index: bool,
Expand All @@ -42,7 +42,7 @@ impl PageHtml {
}
}

pub(crate) trait Content: Display + 'static {
pub(crate) trait PageContent: Display + 'static {
fn title(&self) -> String;

fn page(self, chain: Chain, has_ordinal_index: bool) -> PageHtml
Expand All @@ -67,7 +67,7 @@ mod tests {
}
}

impl Content for Foo {
impl PageContent for Foo {
fn title(&self) -> String {
"Foo".to_string()
}
Expand Down Expand Up @@ -117,7 +117,7 @@ mod tests {
}
}

impl Content for Foo {
impl PageContent for Foo {
fn title(&self) -> String {
"Foo".to_string()
}
Expand Down Expand Up @@ -166,7 +166,7 @@ mod tests {
}
}

impl Content for Foo {
impl PageContent for Foo {
fn title(&self) -> String {
"Foo".to_string()
}
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/server/templates/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl BlockHtml {
}
}

impl Content for BlockHtml {
impl PageContent for BlockHtml {
fn title(&self) -> String {
format!("Block {}", self.hash)
}
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/server/templates/home.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl HomeHtml {
}
}

impl Content for HomeHtml {
impl PageContent for HomeHtml {
fn title(&self) -> String {
"Ordinals".to_string()
}
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/server/templates/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub(crate) struct InputHtml {
pub(crate) input: TxIn,
}

impl Content for InputHtml {
impl PageContent for InputHtml {
fn title(&self) -> String {
format!("Input /{}/{}/{}", self.path.0, self.path.1, self.path.2)
}
Expand Down
Loading

0 comments on commit fff7312

Please sign in to comment.