Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add brotli compression for text-based inscriptions #1713

Merged
merged 60 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
28bfb57
Add initial work
terror Feb 13, 2023
7d0a9a8
Merge branch 'master' into brotli
terror Feb 13, 2023
f1f2055
Remove todo comment
terror Feb 13, 2023
f8fe2bc
Add a bool for compress
terror Feb 13, 2023
b1d21d1
Get it to compile
terror Feb 13, 2023
5ca35a9
Remove comment
terror Feb 13, 2023
249ea63
Merge branch 'master' into brotli
terror Feb 13, 2023
3f2b6d7
Test the compression code path
terror Feb 13, 2023
767565a
Merge branch 'master' into brotli
terror Feb 13, 2023
897f217
Merge branch 'master' into brotli
terror Feb 17, 2023
25efeca
Merge branch 'master' into brotli
terror Feb 18, 2023
4a6fd01
Fix tests
terror Feb 18, 2023
2d58bb8
Remove comment
terror Feb 18, 2023
ea52fc8
Merge branch 'master' into brotli
terror Feb 24, 2023
24b639b
Compress everything
terror Feb 24, 2023
18c68b8
Format
terror Feb 25, 2023
1d93d3f
Use odd value for content encoding
terror Mar 3, 2023
7175437
Merge branch 'master' into brotli
terror Nov 13, 2023
65d633b
Fix tests
terror Nov 13, 2023
1d6957b
Format
terror Nov 13, 2023
f345a06
Merge branch 'master' into brotli
terror Nov 13, 2023
9c73da2
Remove test inscriptions
terror Nov 13, 2023
0e6bad4
Sort
terror Nov 13, 2023
06f5720
Put import in proper place
terror Nov 13, 2023
8eeb192
Sort
terror Nov 13, 2023
961c7f0
Sorty
terror Nov 13, 2023
12c2ad1
Window size best
terror Nov 13, 2023
f495ab1
Give it a compression mode based on content type
terror Nov 13, 2023
92a6315
Sort + add block size
terror Nov 13, 2023
8058710
Simplify
terror Nov 13, 2023
2983c51
Make string
terror Nov 13, 2023
f311cc1
mode -> compression_mode
terror Nov 13, 2023
601d6e3
Add encoding header if client accepts brotli
terror Nov 13, 2023
639d723
Trim the ting
terror Nov 13, 2023
744d9ca
Sort + tweak error message
terror Nov 13, 2023
9e65b44
Tweak doc
terror Nov 13, 2023
c48a688
Skip formatting table
terror Nov 13, 2023
04523e5
Add serialization + template test
terror Nov 14, 2023
661b025
Accept encoding brotli -> Accept encoding
terror Nov 14, 2023
d4d313c
Allow other content encodings
terror Nov 14, 2023
b15a150
Remove/rearrange tests
terror Nov 14, 2023
d72f99c
Add compression integration tests
terror Nov 14, 2023
ac5b57b
Fix preview bs
terror Nov 14, 2023
aca9720
Fix nits
terror Nov 14, 2023
5f0a2c4
Is acceptable ?!
terror Nov 14, 2023
e49f415
Reference self
terror Nov 14, 2023
4190656
To string it
terror Nov 14, 2023
02f80ad
More nits
terror Nov 15, 2023
6ac111d
Merge branch 'master' into brotli
terror Nov 16, 2023
b02b554
Switch to using dropbox brotli impl
terror Nov 16, 2023
ef2d85c
Fix preview tests
terror Nov 16, 2023
5ed2525
Fix test
casey Nov 16, 2023
be38a2d
Merge branch 'master' into brotli
terror Nov 16, 2023
86d4416
Fix import
terror Nov 16, 2023
1549caa
Ensure we get proper decompressed value
terror Nov 16, 2023
00e1270
Use .as_slice
terror Nov 16, 2023
5f3c3ac
Format table
casey Nov 16, 2023
4c2c13a
Pass header to is_acceptable
casey Nov 16, 2023
5faaf54
Remove useless tests
terror Nov 16, 2023
d0947e5
Merge branch 'master' into brotli
casey Nov 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ bech32 = "0.9.1"
bip39 = "2.0.0"
bitcoin = { version = "0.30.1", features = ["rand"] }
boilerplate = { version = "1.0.0", features = ["axum"] }
brotlic = "0.8.1"
chrono = "0.4.19"
ciborium = "0.2.1"
clap = { version = "4.4.2", features = ["derive"] }
Expand Down Expand Up @@ -63,7 +64,7 @@ tower-http = { version = "0.4.0", features = ["compression-br", "compression-gzi
[dev-dependencies]
executable-path = "1.0.0"
pretty_assertions = "1.2.1"
reqwest = { version = "0.11.10", features = ["blocking", "json"] }
reqwest = { version = "0.11.10", features = ["blocking", "brotli", "json"] }
test-bitcoincore-rpc = { path = "test-bitcoincore-rpc" }
unindent = "0.2.1"

Expand Down
36 changes: 29 additions & 7 deletions src/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub(crate) const POINTER_TAG: [u8; 1] = [2];
pub(crate) const PARENT_TAG: [u8; 1] = [3];
pub(crate) const METADATA_TAG: [u8; 1] = [5];
pub(crate) const METAPROTOCOL_TAG: [u8; 1] = [7];
pub(crate) const CONTENT_ENCODING_TAG: [u8; 1] = [9];

type Result<T> = std::result::Result<T, script::Error>;
type RawEnvelope = Envelope<Vec<Vec<u8>>>;
Expand Down Expand Up @@ -80,8 +81,9 @@ impl From<RawEnvelope> for ParsedEnvelope {
let content_type = remove_field(&mut fields, &CONTENT_TYPE_TAG);
terror marked this conversation as resolved.
Show resolved Hide resolved
let parent = remove_field(&mut fields, &PARENT_TAG);
let pointer = remove_field(&mut fields, &POINTER_TAG);
let metaprotocol = remove_field(&mut fields, &METAPROTOCOL_TAG);
let metadata = remove_and_concatenate_field(&mut fields, &METADATA_TAG);
let metaprotocol = remove_field(&mut fields, &METAPROTOCOL_TAG);
let content_encoding = remove_field(&mut fields, &CONTENT_ENCODING_TAG);

let unrecognized_even_field = fields
.keys()
Expand All @@ -96,14 +98,15 @@ impl From<RawEnvelope> for ParsedEnvelope {
.cloned()
.collect()
}),
content_encoding,
content_type,
parent,
pointer,
unrecognized_even_field,
duplicate_field,
incomplete_field,
metaprotocol,
metadata,
metaprotocol,
parent,
pointer,
unrecognized_even_field,
},
input: envelope.input,
offset: envelope.offset,
Expand Down Expand Up @@ -394,13 +397,32 @@ mod tests {
}

#[test]
fn with_unknown_tag() {
fn with_content_encoding() {
assert_eq!(
parse(&[envelope(&[
b"ord",
&[1],
b"text/plain;charset=utf-8",
&[9],
b"br",
&[],
b"ord",
])]),
vec![ParsedEnvelope {
payload: inscription_with_encoding("text/plain;charset=utf-8", "br", "ord"),
..Default::default()
}]
);
}

#[test]
fn with_unknown_tag() {
assert_eq!(
parse(&[envelope(&[
b"ord",
&[1],
b"text/plain;charset=utf-8",
&[11],
b"bar",
&[],
b"ord",
Expand Down Expand Up @@ -730,7 +752,7 @@ mod tests {
#[test]
fn unknown_odd_fields_are_ignored() {
assert_eq!(
parse(&[envelope(&[b"ord", &[9], &[0]])]),
parse(&[envelope(&[b"ord", &[11], &[0]])]),
vec![ParsedEnvelope {
payload: Inscription::default(),
..Default::default()
Expand Down
91 changes: 80 additions & 11 deletions src/inscription.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use {
},
ScriptBuf,
},
io::Cursor,
brotlic::{BlockSize, BrotliEncoderOptions, CompressorWriter, Quality, WindowSize},
io::{Cursor, Write},
std::str,
};

Expand All @@ -26,6 +27,7 @@ pub(crate) enum Curse {
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Default)]
pub struct Inscription {
pub body: Option<Vec<u8>>,
pub content_encoding: Option<Vec<u8>>,
pub content_type: Option<Vec<u8>>,
pub duplicate_field: bool,
pub incomplete_field: bool,
Expand All @@ -38,9 +40,14 @@ pub struct Inscription {

impl Inscription {
#[cfg(test)]
pub(crate) fn new(content_type: Option<Vec<u8>>, body: Option<Vec<u8>>) -> Self {
terror marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) fn new(
content_type: Option<Vec<u8>>,
content_encoding: Option<Vec<u8>>,
body: Option<Vec<u8>>,
) -> Self {
Self {
content_type,
content_encoding,
body,
..Default::default()
}
Expand All @@ -53,23 +60,51 @@ impl Inscription {
pointer: Option<u64>,
metaprotocol: Option<String>,
metadata: Option<Vec<u8>>,
compress: bool,
) -> Result<Self, Error> {
let path = path.as_ref();

let body = fs::read(path).with_context(|| format!("io error reading {}", path.display()))?;

let (content_type, compression_mode) = Media::content_type(path)?;

let (body, content_encoding) = if compress {
let encoder = BrotliEncoderOptions::new()
.block_size(BlockSize::best())
.mode(compression_mode)
.quality(Quality::best())
.size_hint(body.len().try_into().unwrap_or(u32::MAX))
.window_size(WindowSize::best())
.build()?;

let mut compressor = CompressorWriter::with_encoder(encoder, Vec::new());

compressor
.write_all(&body)
.with_context(|| "failed to compress inscription")?;

let compressed = compressor.into_inner().unwrap();

if compressed.len() < body.len() {
(compressed, Some("br".as_bytes().to_vec()))
} else {
(body, None)
}
} else {
(body, None)
};

if let Some(limit) = chain.inscription_content_size_limit() {
let len = body.len();
if len > limit {
bail!("content size of {len} bytes exceeds {limit} byte limit for {chain} inscriptions");
}
}

let content_type = Media::content_type_for_path(path)?;

Ok(Self {
body: Some(body),
content_type: Some(content_type.into()),
content_encoding,
metadata,
metaprotocol: metaprotocol.map(|metaprotocol| metaprotocol.into_bytes()),
parent: parent.map(|id| id.parent_value()),
Expand Down Expand Up @@ -103,6 +138,12 @@ impl Inscription {
.push_slice(PushBytesBuf::try_from(content_type).unwrap());
}

if let Some(content_encoding) = self.content_encoding.clone() {
builder = builder
.push_slice(envelope::CONTENT_ENCODING_TAG)
.push_slice(PushBytesBuf::try_from(content_encoding).unwrap());
}

if let Some(protocol) = self.metaprotocol.clone() {
builder = builder
.push_slice(envelope::METAPROTOCOL_TAG)
Expand Down Expand Up @@ -189,6 +230,10 @@ impl Inscription {
str::from_utf8(self.content_type.as_ref()?).ok()
}

pub(crate) fn content_encoding(&self) -> Option<&str> {
terror marked this conversation as resolved.
Show resolved Hide resolved
str::from_utf8(self.content_encoding.as_ref()?).ok()
}

pub(crate) fn metadata(&self) -> Option<Value> {
ciborium::from_reader(Cursor::new(self.metadata.as_ref()?)).ok()
}
Expand Down Expand Up @@ -670,22 +715,46 @@ mod tests {
write!(file, "foo").unwrap();

let inscription =
Inscription::from_file(Chain::Mainnet, file.path(), None, None, None, None).unwrap();
Inscription::from_file(Chain::Mainnet, file.path(), None, None, None, None, false).unwrap();

assert_eq!(inscription.pointer, None);

let inscription =
Inscription::from_file(Chain::Mainnet, file.path(), None, Some(0), None, None).unwrap();
let inscription = Inscription::from_file(
Chain::Mainnet,
file.path(),
None,
Some(0),
None,
None,
false,
)
.unwrap();

assert_eq!(inscription.pointer, Some(Vec::new()));

let inscription =
Inscription::from_file(Chain::Mainnet, file.path(), None, Some(1), None, None).unwrap();
let inscription = Inscription::from_file(
Chain::Mainnet,
file.path(),
None,
Some(1),
None,
None,
false,
)
.unwrap();

assert_eq!(inscription.pointer, Some(vec![1]));

let inscription =
Inscription::from_file(Chain::Mainnet, file.path(), None, Some(256), None, None).unwrap();
let inscription = Inscription::from_file(
Chain::Mainnet,
file.path(),
None,
Some(256),
None,
None,
false,
)
.unwrap();

assert_eq!(inscription.pointer, Some(vec![0, 1]));
}
Expand Down
Loading