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

DA compression for fuel-tx types #670

Merged
merged 82 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
eddf4cb
WIP
Dentosal Jan 29, 2024
bf61b73
Migrate compact-derive from fuel-core
Dentosal Feb 1, 2024
1b0917e
Merge branch 'master' into dento/da-compression
Dentosal Feb 5, 2024
0c7f3dd
Add doc comments and cleanup
Dentosal Feb 5, 2024
cbc2173
Add changelog
Dentosal Feb 5, 2024
82b2efa
Cargo.toml fmt
Dentosal Feb 5, 2024
1e792e9
Wrap Compaction return types to anyhow::Result
Dentosal Feb 12, 2024
6ad66c3
Update readme
Dentosal Feb 12, 2024
cc1b7a8
Fix wording
Dentosal Mar 6, 2024
8f60962
Combine some bounds
Dentosal Mar 6, 2024
3169f6c
Merge branch 'master' into dento/da-compression
Dentosal Jul 16, 2024
ac811f5
Fix issues after merge
Dentosal Jul 16, 2024
8ed0672
Fix compression for latest types
Dentosal Jul 17, 2024
f04df70
Keep used gas price
Dentosal Jul 17, 2024
141efe5
Cargo sort
Dentosal Jul 17, 2024
da7819f
Introduce ContractId table to da compression registry
Dentosal Jul 18, 2024
42e8634
Reference table types directly in #[da_compress(registry = _)]
Dentosal Jul 18, 2024
781fe1e
WIP: migrate domain logic to fuel-core
Dentosal Jul 22, 2024
546dda0
Re-add dummy registry for testing
Dentosal Jul 22, 2024
bf2b976
Add some missing docs
Dentosal Jul 22, 2024
880d13c
Move block section data for fuel-core
Dentosal Jul 22, 2024
b23bff5
Minor change for fuel-core integration
Dentosal Jul 22, 2024
293f0f0
cargo sort
Dentosal Jul 22, 2024
a1b5174
Fix incorrect feature cfg on default_test_tx
Dentosal Jul 23, 2024
dd40e5b
Merge branch 'master' into dento/da-compression
Dentosal Aug 27, 2024
f7d6ced
Implement compacting for blobs as well
Dentosal Aug 27, 2024
21b4ab6
Fix no_std deps
Dentosal Aug 27, 2024
4188f37
WIP: working towards adapting Green's trait-based approach
Dentosal Sep 2, 2024
f0be2b1
Complete migration to Green's trait-heavy approach
Dentosal Sep 3, 2024
0b8a0d9
Polish: docs, naming, argument order
Dentosal Sep 3, 2024
69ac157
Clean up proc macro a bit
Dentosal Sep 3, 2024
1ef6803
Line count reduction :\
Dentosal Sep 3, 2024
07391b2
More polish
Dentosal Sep 3, 2024
6b359f0
Move readme from fuel-compression to fuel-core-compression
Dentosal Sep 3, 2024
3ee7589
Enable more lints for fuel-derive, remove unnecessary regex dependency
Dentosal Sep 3, 2024
1530755
Remove debug writing to /tmp, as we're hopefully done here
Dentosal Sep 3, 2024
f1e7114
Get rid of anyhow
Dentosal Sep 3, 2024
31083c1
Add roundtrip tests
Dentosal Sep 3, 2024
0cc0be0
Use type-based registry keyspaces
Dentosal Sep 3, 2024
2a7904a
Omit Message amount from DA compressed data
Dentosal Sep 3, 2024
b2861d7
Remove extra "Compressible" bound from "RegistrySubstitutableBy"
Dentosal Sep 3, 2024
4f7a2d6
Implement da compression for TxId -> TxPointer
Dentosal Sep 3, 2024
c984fa1
Use the type instead of field annotations for determining compressed …
Dentosal Sep 4, 2024
b947dae
Make da compression async
Dentosal Sep 4, 2024
5d9087d
Also make compression context async
Dentosal Sep 4, 2024
3d2af6d
unused_crate_dependencies fix
Dentosal Sep 4, 2024
19aa0c0
More unused_crate_dependencies
Dentosal Sep 4, 2024
9a80b08
cargo sort
Dentosal Sep 4, 2024
af39dcf
Cosmetic changes:
xgreenx Sep 4, 2024
bf75dbc
Cleanup small nits after review
xgreenx Sep 4, 2024
cc1b892
Revert commits af39dcfcf19d6e41cca6fb39a6707b8614854338 and bf75dbc13…
Dentosal Sep 5, 2024
b8011ce
Split derive macro "Compressed" into "Compress" and "Decompress"
Dentosal Sep 5, 2024
34c7a23
Re-apply Green's da_compress(bound) removal and bound simplification
Dentosal Sep 5, 2024
0342ae1
Re-introduce Green's TxId type, do some cleanup related to these types
Dentosal Sep 5, 2024
285cf88
Re-apply bf75dbc1354840fb7787d1282821b241d4bb8c89
Dentosal Sep 5, 2024
b3e0718
Compress the whole UtxoId instead of just TxId
Dentosal Sep 5, 2024
ee39490
Compress UtxoId: test fixes
Dentosal Sep 5, 2024
7aa5405
Introduce PredicateCode to allow compressing it
Dentosal Sep 5, 2024
1bb7090
Make fields for Compressed* public
Dentosal Sep 5, 2024
d23d6cd
Remove Decompress derive from Message and Coin
Dentosal Sep 5, 2024
efd2dc6
Rename da_compress attribute to just compress
Dentosal Sep 5, 2024
92983a9
Add tests for TxId
Dentosal Sep 5, 2024
7b88695
Rename remaining instances of compaction into compression
Dentosal Sep 5, 2024
b60e38a
Add explicit CompressedUtxoId type
Dentosal Sep 5, 2024
89654f1
Rename De/CompressionContext methods
Dentosal Sep 6, 2024
d9c3aa3
Clippy
Dentosal Sep 6, 2024
7a98ae4
Ignore incorrect unused crate warning
Dentosal Sep 6, 2024
7e6a865
Fix no-default-features
Dentosal Sep 6, 2024
9b33143
Remove De/CompressionContext traits, they seem unnecessary
Dentosal Sep 6, 2024
cc61e1e
Simplifying tests for DA compression (#816)
xgreenx Sep 9, 2024
0fe904a
Merge branch 'master' into dento/da-compression
Dentosal Sep 9, 2024
5728192
Address PR review comments
Dentosal Sep 9, 2024
6356189
Merge branch 'dento/da-compression' of https://github.com/FuelLabs/fu…
Dentosal Sep 9, 2024
860dc98
Fix , => ;
Dentosal Sep 9, 2024
987fdbb
Fix a memory leak in unsafe array de/compress code
Dentosal Sep 9, 2024
90c71df
Fix typo
Dentosal Sep 9, 2024
e80ee5d
Fix typo
Dentosal Sep 9, 2024
cbe48f9
Add a test cases to cover skipped fields and nested structs
Dentosal Sep 10, 2024
b0f52dd
Remove unneeded change of field ref for struct that impls Deref
Dentosal Sep 10, 2024
1601729
Remove useless .scollect()s
Dentosal Sep 10, 2024
59ffbd5
Clarify variable namings around serialization in a test case
Dentosal Sep 10, 2024
476c898
Add a test case showing that skipped fields are not part of the compr…
Dentosal Sep 10, 2024
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Added

- [#670](https://github.com/FuelLabs/fuel-vm/pull/670): Add DA compression functionality to `Transaction` and any types within

### Changed

#### Breaking

- [#670](https://github.com/FuelLabs/fuel-vm/pull/670): The `predicate` field of `fuel_tx::input::Coin` is now a wrapper struct `PredicateCode`.

## [Version 0.56.0]

### Added
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"fuel-asm",
"fuel-compression",
"fuel-crypto",
"fuel-merkle",
"fuel-storage",
Expand All @@ -22,6 +23,7 @@ version = "0.56.0"
[workspace.dependencies]
fuel-asm = { version = "0.56.0", path = "fuel-asm", default-features = false }
fuel-crypto = { version = "0.56.0", path = "fuel-crypto", default-features = false }
fuel-compression = { version = "0.56.0", path = "fuel-compression", default-features = false }
fuel-derive = { version = "0.56.0", path = "fuel-derive", default-features = false }
fuel-merkle = { version = "0.56.0", path = "fuel-merkle", default-features = false }
fuel-storage = { version = "0.56.0", path = "fuel-storage", default-features = false }
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and [the Sway compiler](https://github.com/FuelLabs/sway/).
| Crate | Version | Description |
|--------------|-----------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| fuel-asm | [![crates.io](https://img.shields.io/crates/v/fuel-asm)](https://crates.io/crates/fuel-asm) | Contains the FuelVM instruction set - opcodes used by the Sway and VM. |
| fuel-compression | [![crates.io](https://img.shields.io/crates/v/fuel-compression)](https://crates.io/crates/fuel-compression) | DA-layer compression of Fuel transaction types |
| fuel-crypto | [![crates.io](https://img.shields.io/crates/v/fuel-crypto)](https://crates.io/crates/fuel-crypto) | Cryptographic primitives used across Fuel Rust based projects. |
| fuel-merkle | [![crates.io](https://img.shields.io/crates/v/fuel-merkle)](https://crates.io/crates/fuel-merkle) | Implementations of the Merkle Tree used by the `fuel-core` to fulfill fraud proofs requirements, and `fuel-tx` to validate transaction validity. |
| fuel-storage | [![crates.io](https://img.shields.io/crates/v/fuel-storage)](https://crates.io/crates/fuel-storage) | Storage abstraction is used to connect FuelVM, `fuel-merkle`, and `fuel-core` together without direct access. |
Expand Down
2 changes: 1 addition & 1 deletion fuel-asm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ description = "Atomic types of the FuelVM."
[dependencies]
arbitrary = { version = "1.1", features = ["derive"], optional = true }
bitflags = { workspace = true }
fuel-types = { workspace = true }
fuel-types = { workspace = true, default-features = false }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
strum = { version = "0.24", default-features = false, features = ["derive"] }
wasm-bindgen = { version = "0.2.88", optional = true }
Expand Down
16 changes: 16 additions & 0 deletions fuel-compression/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "fuel-compression"
version = { workspace = true }
authors = { workspace = true }
categories = ["cryptography::cryptocurrencies"]
edition = { workspace = true }
homepage = { workspace = true }
keywords = ["blockchain", "cryptocurrencies", "fuel-compression"]
license = { workspace = true }
repository = { workspace = true }
description = "Compression and decompression of Fuel blocks for DA storage."

[dependencies]
fuel-derive = { workspace = true }
fuel-types = { workspace = true }
serde = { version = "1.0", features = ["derive"] }
176 changes: 176 additions & 0 deletions fuel-compression/src/impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
//! Trait impls for Rust types

use super::traits::*;
use crate::RegistryKey;
use core::mem::MaybeUninit;
use fuel_types::{
Address,
AssetId,
BlobId,
BlockHeight,
Bytes32,
ContractId,
Nonce,
Salt,
};

macro_rules! identity_compaction {
($t:ty) => {
impl Compressible for $t {
type Compressed = Self;
}

impl<Ctx, E> CompressibleBy<Ctx, E> for $t
where
Ctx: ?Sized,
{
async fn compress(&self, _: &mut Ctx) -> Result<Self, E> {
Ok(*self)
}
}

impl<Ctx, E> DecompressibleBy<Ctx, E> for $t
where
Ctx: ?Sized,
{
async fn decompress(c: &Self::Compressed, _: &Ctx) -> Result<Self, E> {
Ok(*c)
}
}
};
}

identity_compaction!(u8);
identity_compaction!(u16);
identity_compaction!(u32);
identity_compaction!(u64);
identity_compaction!(u128);

identity_compaction!(BlockHeight);
identity_compaction!(BlobId);
identity_compaction!(Bytes32);
identity_compaction!(Salt);
identity_compaction!(Nonce);

macro_rules! array_types_compaction {
($t:ty, $compressed_t:ty) => {
impl Compressible for $t {
type Compressed = $compressed_t;
}

impl<Ctx, E> CompressibleBy<Ctx, E> for $t
where
Ctx: CompressionContext<$t, Error = E>,
Ctx: ?Sized,
{
async fn compress(&self, ctx: &mut Ctx) -> Result<$compressed_t, E> {
ctx.compress(self).await
}
}

impl<Ctx, E> DecompressibleBy<Ctx, E> for $t
where
Ctx: DecompressionContext<$t, Error = E>,
Ctx: ?Sized,
{
async fn decompress(value: &Self::Compressed, ctx: &Ctx) -> Result<$t, E> {
ctx.decompress(value).await
}
}
};
}

array_types_compaction!(Address, RegistryKey);
array_types_compaction!(ContractId, RegistryKey);
array_types_compaction!(AssetId, RegistryKey);

impl<const S: usize, T> Compressible for [T; S]
where
T: Compressible,
{
type Compressed = [T::Compressed; S];
}

impl<const S: usize, T, Ctx, E> CompressibleBy<Ctx, E> for [T; S]
where
T: CompressibleBy<Ctx, E>,
{
#[allow(unsafe_code)]
async fn compress(&self, ctx: &mut Ctx) -> Result<Self::Compressed, E> {
// SAFETY: we are claiming to have initialized an array of `MaybeUninit`s,
// which do not require initialization.
let mut tmp: [MaybeUninit<T::Compressed>; S] =
unsafe { MaybeUninit::uninit().assume_init() };

// Dropping a `MaybeUninit` does nothing, so we can just overwrite the array.
// TODO: Handle the case of the error. Currently it will cause a memory leak.
// https://github.com/FuelLabs/fuel-vm/issues/811
for (v, empty) in self.iter().zip(tmp.iter_mut()) {
unsafe {
core::ptr::write(empty.as_mut_ptr(), v.compress(ctx).await?);
}
}

// SAFETY: Every element is initialized.
let result = tmp.map(|v| unsafe { v.assume_init() });
Ok(result)
}
}

impl<const S: usize, T, Ctx, E> DecompressibleBy<Ctx, E> for [T; S]
where
T: DecompressibleBy<Ctx, E> + Clone,
{
#[allow(unsafe_code)]
async fn decompress(c: &Self::Compressed, ctx: &Ctx) -> Result<Self, E> {
// SAFETY: we are claiming to have initialized an array of `MaybeUninit`s,
// which do not require initialization.
let mut tmp: [MaybeUninit<T>; S] = unsafe { MaybeUninit::uninit().assume_init() };

// Dropping a `MaybeUninit` does nothing, so we can just overwrite the array.
// TODO: Handle the case of the error. Currently it will cause a memory leak.
// https://github.com/FuelLabs/fuel-vm/issues/811
for (v, empty) in c.iter().zip(tmp.iter_mut()) {
unsafe {
core::ptr::write(empty.as_mut_ptr(), T::decompress(v, ctx).await?);
}
}

// SAFETY: Every element is initialized.
let result = tmp.map(|v| unsafe { v.assume_init() });
Ok(result)
}
}

impl<T> Compressible for Vec<T>
where
T: Compressible + Clone,
{
type Compressed = Vec<T::Compressed>;
}

impl<T, Ctx, E> CompressibleBy<Ctx, E> for Vec<T>
where
T: CompressibleBy<Ctx, E> + Clone,
{
async fn compress(&self, ctx: &mut Ctx) -> Result<Self::Compressed, E> {
let mut result = Vec::with_capacity(self.len());
for item in self {
result.push(item.compress(ctx).await?);
}
Ok(result)
}
}

impl<T, Ctx, E> DecompressibleBy<Ctx, E> for Vec<T>
where
T: DecompressibleBy<Ctx, E> + Clone,
{
async fn decompress(c: &Self::Compressed, ctx: &Ctx) -> Result<Self, E> {
let mut result = Vec::with_capacity(c.len());
for item in c {
result.push(T::decompress(item, ctx).await?);
}
Ok(result)
}
}
69 changes: 69 additions & 0 deletions fuel-compression/src/key.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use serde::{
Deserialize,
Serialize,
};

/// Untyped key pointing to a registry table entry.
/// The last key (all bits set) is reserved for the default value and cannot be written
/// to.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct RegistryKey([u8; Self::SIZE]);
impl RegistryKey {
/// Key mapping to default value for the table type.
pub const DEFAULT_VALUE: Self = Self([u8::MAX; Self::SIZE]);
/// Maximum writable key.
pub const MAX_WRITABLE: Self = Self([u8::MAX, u8::MAX, u8::MAX - 1]);
/// Size of the key, in bytes.
pub const SIZE: usize = 3;
/// Zero key.
pub const ZERO: Self = Self([0; Self::SIZE]);

/// Convert to u32, big-endian.
pub fn as_u32(self) -> u32 {
u32::from_be_bytes([0, self.0[0], self.0[1], self.0[2]])
}

/// Wraps around just below max/default value.
/// Panics for max/default value.
pub fn next(self) -> Self {
if self == Self::DEFAULT_VALUE {
panic!("Max/default value has no next key");
}
let next_raw = self.as_u32() + 1u32;
if next_raw == Self::DEFAULT_VALUE.as_u32() {
Self::ZERO
} else {
Self::try_from(next_raw)
.expect("The procedure above always produces a valid key")
}
}
}
impl TryFrom<u32> for RegistryKey {
type Error = &'static str;

fn try_from(value: u32) -> Result<Self, Self::Error> {
let v = value.to_be_bytes();
if v[0] != 0 {
return Err("RegistryKey must be less than 2^24");
}

let mut bytes = [0u8; 3];
rafal-ch marked this conversation as resolved.
Show resolved Hide resolved
bytes.copy_from_slice(&v[1..]);
Ok(Self(bytes))
}
}

#[cfg(test)]
mod tests {
use super::RegistryKey;

#[test]
fn key_next() {
assert_eq!(RegistryKey::ZERO.next(), RegistryKey([0, 0, 1]));
assert_eq!(RegistryKey::ZERO.next().next(), RegistryKey([0, 0, 2]));
assert_eq!(RegistryKey([0, 0, 255]).next(), RegistryKey([0, 1, 0]));
assert_eq!(RegistryKey([0, 1, 255]).next(), RegistryKey([0, 2, 0]));
assert_eq!(RegistryKey([0, 255, 255]).next(), RegistryKey([1, 0, 0]));
assert_eq!(RegistryKey::MAX_WRITABLE.next(), RegistryKey::ZERO);
}
}
19 changes: 19 additions & 0 deletions fuel-compression/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Compression and decompression of fuel-types for the DA layer

#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![warn(missing_docs)]
#![deny(unsafe_code)]
#![deny(unused_crate_dependencies)]
#![deny(clippy::cast_possible_truncation)]

mod impls;
mod key;
mod traits;

pub use key::RegistryKey;
pub use traits::*;

pub use fuel_derive::{
Compress,
Decompress,
};
Comment on lines +16 to +19
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about renaming macros into CompressibleBy and DecompressibleBy to show which traits are implemented?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the point in having the same name here. I dislike it since the names are confusing. When I see

#[derive(fuel_compression::Compress)]

I know this type can be compressed by some fuel_compression-related machinery. When I see

#[derive(fuel_compression::CompressibleBy)]

I'm like compressible by what? Compressible might be an improvement.

Also, Compress implements both Compressible and CompressibleBy, and the define macros having the same names as the trait obfuscates this fact even more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, Compress implements both Compressible and CompressibleBy, and the define macros having the same names as the trait obfuscates this fact even more.

Yeah, it is true. But Compressible is a super trait of CompressibleBy, so it is not so bad=D But I agree that it is hidden property of the macro that can be discovered only from the comment.

But in the case of DecompressibleBy everything is clear=) The macro implements the same trait.

But it is up to you, I will not push it futher=)

62 changes: 62 additions & 0 deletions fuel-compression/src/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#![allow(async_fn_in_trait)] // We control the implementation so this is fine

/// This type can be compressed to a more compact form and back using
/// `CompressibleBy` and `DecompressibleBy` traits.
pub trait Compressible {
/// The compressed type.
type Compressed: Sized;
}

/// A context that can be used to compress a type.
pub trait CompressionContext<Type>
where
Type: Compressible,
{
/// Error when compressing. Note that the compression itself is not faillible,
/// but the context may still do fallible operations.
type Error;

/// Perform compression, returning the compressed data and possibly modifying the
/// context. The context is mutable to allow for stateful compression.
/// For instance, it can be used to extract original data when replacing it with
/// references.
async fn compress(&mut self, value: &Type) -> Result<Type::Compressed, Self::Error>;
}

/// A context that can be used to decompress a type.
pub trait DecompressionContext<Type>
where
Type: Compressible,
{
/// Error when compressing. Note that the compression itself is not faillible,
/// but the context may still do fallible operations.
type Error;

/// Perform decompression, returning the original data.
/// The context can be used to resolve references.
async fn decompress(&self, value: &Type::Compressed) -> Result<Type, Self::Error>;
}

/// This type can be compressed to a more compact form and back using
/// `CompressionContext`.
pub trait CompressibleBy<Ctx, E>: Compressible
where
Ctx: ?Sized,
{
/// Perform compression, returning the compressed data and possibly modifying the
/// context. The context is mutable to allow for stateful compression.
/// For instance, it can be used to extract original data when replacing it with
/// references.
async fn compress(&self, ctx: &mut Ctx) -> Result<Self::Compressed, E>;
}

/// This type can be decompressed using `CompressionContext`.
pub trait DecompressibleBy<Ctx, E>: Compressible
where
Ctx: ?Sized,
Self: Sized,
{
/// Perform decompression, returning the original data.
/// The context can be used to resolve references.
async fn decompress(c: &Self::Compressed, ctx: &Ctx) -> Result<Self, E>;
}
Loading
Loading