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

feat: Update contract leaf size #450

Merged
merged 27 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6b29ade
Update contract leaf size
bvrooman May 18, 2023
d2dae31
Fix clippy
bvrooman May 22, 2023
022c3cb
Update snapshots
bvrooman May 22, 2023
5b026ad
Add comments
bvrooman May 22, 2023
3a73a8f
Update contract.rs
bvrooman May 22, 2023
7b6e197
Multi leaf snapshot test
bvrooman May 22, 2023
8acf59d
Add test for padding
bvrooman May 23, 2023
56a5429
Merge branch 'master' into bvrooman/feat/update-contract-leaf-size
May 23, 2023
7e11b85
Partial leaf state root test
bvrooman May 24, 2023
b4e3afc
Parameterize tests
bvrooman May 24, 2023
ab183e9
Merge branch 'master' into bvrooman/feat/update-contract-leaf-size
May 24, 2023
1ee526f
Use constant for multiple
bvrooman May 24, 2023
cf5e378
Merge branch 'bvrooman/feat/update-contract-leaf-size' of https://git…
bvrooman May 24, 2023
abfb76a
Clippy
bvrooman May 24, 2023
6d50f04
Merge branch 'master' into bvrooman/feat/update-contract-leaf-size
xgreenx May 24, 2023
3699022
Update fuel-tx/src/contract.rs
May 24, 2023
177166e
Pad only if final leaf is not a multiple
bvrooman May 24, 2023
44ca83a
Simplify padded leaf creation
bvrooman May 24, 2023
c845d97
Update check
bvrooman May 24, 2023
c1d5089
Update CHANGELOG.md
bvrooman May 24, 2023
a2aca31
Update CHANGELOG.md
May 24, 2023
f8e5f86
Merge branch 'master' into bvrooman/feat/update-contract-leaf-size
May 24, 2023
2e34d9a
Update CHANGELOG.md
bvrooman May 24, 2023
6c50d34
Merge branch 'bvrooman/feat/update-contract-leaf-size' of https://git…
bvrooman May 24, 2023
e488873
Merge branch 'master' into bvrooman/feat/update-contract-leaf-size
May 24, 2023
8aaf009
Merge branch 'master' into bvrooman/feat/update-contract-leaf-size
May 24, 2023
45553f0
Use array over vec
bvrooman May 24, 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ and `Input::predicate_owner` use `ChainId` instead of the `ConsensusParameters`.
It is a less strict requirement than before because you can get `ChainId`
from `ConsensusParameters.chain_id`, and it makes the API cleaner.
It affects all downstream functions that use listed methods.
- [#450](https://github.com/FuelLabs/fuel-vm/pull/450): The Merkle root of a contract's code is now calculated by partitioning the code into chunks of 16 KiB, instead of 8 bytes. If the last leaf is does not a full 16 KiB, it is padded with `0` up to the nearest multiple of 8 bytes. This affects the `ContractId` and `PredicateId` calculations, breaking all code that used hardcoded values.

- [#386](https://github.com/FuelLabs/fuel-vm/pull/386): Several methods of the `TransactionFee` are renamed `total` -> `max_fee`
and `bytes` -> `min_fee`. The `TransactionFee::min_fee` take into account the gas used by predicates.
Expand Down
154 changes: 134 additions & 20 deletions fuel-tx/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,27 @@ use derivative::Derivative;
use fuel_crypto::Hasher;
use fuel_merkle::binary::in_memory::MerkleTree as BinaryMerkleTree;
use fuel_merkle::sparse::in_memory::MerkleTree as SparseMerkleTree;
use fuel_types::{fmt_truncated_hex, Bytes32, Bytes8, ContractId, Salt};
use fuel_types::{fmt_truncated_hex, Bytes32, ContractId, Salt};

use alloc::vec::Vec;
use core::iter;

/// The target size of Merkle tree leaves in bytes. Contract code will will be divided into chunks
/// of this size and pushed to the Merkle tree.
///
/// See https://github.com/FuelLabs/fuel-specs/blob/master/src/protocol/id/contract.md#contract-id
const LEAF_SIZE: usize = 16 * 1024;
/// In the event that contract code cannot be divided evenly by the `LEAF_SIZE`, the remainder must
/// be padded to the nearest multiple of 8 bytes. Padding is achieved by repeating the
/// `PADDING_BYTE`.
const PADDING_BYTE: u8 = 0u8;
const MULTIPLE: usize = 8;

/// See https://stackoverflow.com/a/9194117
fn next_multiple<const N: usize>(x: usize) -> usize {
x + (N - x % N) % N
}

#[derive(Default, Derivative, Clone, PartialEq, Eq, Hash)]
#[derivative(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand All @@ -29,25 +45,20 @@ impl Contract {
B: AsRef<[u8]>,
{
let mut tree = BinaryMerkleTree::new();

bytes
.as_ref()
.chunks(Bytes8::LEN)
.map(|c| {
if c.len() == Bytes8::LEN {
Bytes8::new(c.try_into().expect("checked len chunk"))
} else {
// Potential collision with non-padded input. Consider adding an extra leaf
// for padding?
let mut b = [0u8; 8];

let l = c.len();
b[..l].copy_from_slice(c);

b.into()
}
})
.for_each(|l| tree.push(l.as_ref()));
bytes.as_ref().chunks(LEAF_SIZE).for_each(|leaf| {
// If the bytecode is not a multiple of LEAF_SIZE, the final leaf
// should be zero-padded rounding up to the nearest multiple of 8
// bytes.
let len = leaf.len();
if len == LEAF_SIZE || len % MULTIPLE == 0 {
tree.push(leaf);
} else {
let padding_size = next_multiple::<MULTIPLE>(len);
let mut padded_leaf = [PADDING_BYTE; LEAF_SIZE];
padded_leaf[0..len].clone_from_slice(leaf);
tree.push(padded_leaf[..padding_size].as_ref());
}
});

tree.root().into()
}
Expand Down Expand Up @@ -193,4 +204,107 @@ mod tests {
let default_root = Contract::default_state_root();
insta::assert_debug_snapshot!(default_root);
}

Copy link
Member

@Voxelot Voxelot May 23, 2023

Choose a reason for hiding this comment

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

should we have a unit test to verify padding behavior?

e.g. compare with a manually constructed leaf vs the chunking algorithm above

#[test]
fn multi_leaf_state_root_snapshot() {
let mut rng = StdRng::seed_from_u64(0xF00D);
// 5 full leaves and a partial 6th leaf with 4 bytes of data
let partial_leaf_size = 4;
let code_len = 5 * LEAF_SIZE + partial_leaf_size;
let mut code = alloc::vec![0u8; code_len];
rng.fill_bytes(code.as_mut_slice());

// compute root
let root = Contract::root_from_code(code);

// take root snapshot
insta::with_settings!(
{snapshot_suffix => "multi-leaf-state-root"},
{
insta::assert_debug_snapshot!(root);
}
);
}

#[rstest]
#[case(0)]
#[case(1)]
#[case(8)]
#[case(500)]
#[case(1000)]
#[case(1024)]
#[case(1025)]
fn partial_leaf_state_root(#[case] partial_leaf_size: usize) {
let mut rng = StdRng::seed_from_u64(0xF00D);
let code_len = partial_leaf_size;
let mut code = alloc::vec![0u8; code_len];
rng.fill_bytes(code.as_mut_slice());

// Compute root
let root = Contract::root_from_code(code.clone());

// Compute expected root
let expected_root = {
let mut tree = BinaryMerkleTree::new();

// Push partial leaf with manual padding.
// We start by generating an n-byte array, where n is the code
// length rounded to the nearest multiple of 8, and each byte is the
// PADDING_BYTE by default. The leaf is generated by copying the
// remaining data bytes into the start of this array.
let sz = next_multiple::<8>(partial_leaf_size);
if sz > 0 {
let mut padded_leaf = vec![PADDING_BYTE; sz];
padded_leaf[0..code_len].clone_from_slice(&code);
tree.push(&padded_leaf);
}
tree.root().into()
};

assert_eq!(root, expected_root);
}

#[rstest]
#[case(0)]
#[case(1)]
#[case(8)]
#[case(500)]
#[case(1000)]
#[case(1024)]
#[case(1025)]
fn multi_leaf_state_root(#[case] partial_leaf_size: usize) {
let mut rng = StdRng::seed_from_u64(0xF00D);
// 3 full leaves and a partial 4th leaf
let code_len = 3 * LEAF_SIZE + partial_leaf_size;
let mut code = alloc::vec![0u8; code_len];
rng.fill_bytes(code.as_mut_slice());

// Compute root
let root = Contract::root_from_code(code.clone());

// Compute expected root
let expected_root = {
let mut tree = BinaryMerkleTree::new();

let leaves = code.chunks(LEAF_SIZE).into_iter().collect::<Vec<_>>();
tree.push(leaves[0]);
tree.push(leaves[1]);
tree.push(leaves[2]);

// Push partial leaf with manual padding.
// We start by generating an n-byte array, where n is the code
// length rounded to the nearest multiple of 8, and each byte is the
// PADDING_BYTE by default. The leaf is generated by copying the
// remaining data bytes into the start of this array.
let sz = next_multiple::<8>(partial_leaf_size);
if sz > 0 {
let mut padded_leaf = vec![PADDING_BYTE; sz];
padded_leaf[0..partial_leaf_size].clone_from_slice(leaves[3]);
tree.push(&padded_leaf);
}
tree.root().into()
};

assert_eq!(root, expected_root);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/contract.rs
source: fuel-tx/src/contract.rs
expression: root
---
0xdfd9ba908fe30fa53247b1b8c4a37b02a62e8470c24b9d8bf2e01e91dd21edb9
0xa76dcae4273663c47ad70ea4b7a0910966a940fc69f49c7dbf1f8a91a9343398
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/contract.rs
source: fuel-tx/src/contract.rs
expression: root
---
0x2e98c3969a9dd6195000957d4c63f719bb1c456ead6a10c812f3db225ee8a290
0xcf783d15cd098add2e2fbec2d68ac40838fd7d2801750af65684b141f69adb09
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/contract.rs
source: fuel-tx/src/contract.rs
expression: root
---
0x8c02461a4b9aee7faf2e8244ddd0bb9ad8e30bf4e16528d7dcaa8ec991f0cb18
0x2a8c1cfb7fa96084eb0e7415af3321a87a66e915aae247f71f4408ffea476e0a
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/contract.rs
source: fuel-tx/src/contract.rs
expression: root
---
0x8f2e2a02e895166cacd261c55cede7c57a9174aa3f7d006ec548f0bac9e83048
0xec264ca4b7571f3c322b79889b04ed6b8c0e2dd9a5c5070c0d9d540111026d23
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/contract.rs
source: fuel-tx/src/contract.rs
expression: root
---
0x72f7ae5aab24e266bd35ae505309d55569bae110f9383b4bada38b134bf6acb2
0x486fb1dccad030b63f0a694c21752774997ae44a18dfe482cb50b8a3c8deb068
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/contract.rs
source: fuel-tx/src/contract.rs
expression: root
---
0xc2f498aa34efbc1a3628564d3d5fd462d27837253fe77b94ac188f0156b41e87
0x56328e86da5dde2e47bdd4119a1d186b0683ca069d4b79e5663667a355fe5db6
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/contract.rs
source: fuel-tx/src/contract.rs
expression: root
---
0xff904a16917de130c801eef68dd89509beed5a3a06e8e81f9be5837a33b4d860
0xaf9fe23938748581d8614d87a171d2cbf4ff465ae0d9efcaece798b231896293
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/contract.rs
source: fuel-tx/src/contract.rs
expression: root
---
0x7bf45deb56341e3a7c6904a756724a1b1d6fc23c963c688536d382db01734498
0xccb9334aa0212f5ddbffd160bd1dd0594c36ffdff8ee34a2751d7158d318055e
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/contract.rs
source: fuel-tx/src/contract.rs
expression: root
---
0xb2728a5291dc67b0bc8299a2268f8f6197d7fcc9dceead7c27916102f519f718
0x8e3dc4153c1c687713bade23a179fc4d419b37f2ff278c59de5fc9c535be7c8c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
source: fuel-tx/src/contract.rs
expression: root
---
0x573bf26ecbfae1dfe079dbcea08defc35e280787b0be24a4683c0e3a48cee837