-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add function for generating bytecode identifier (#6674)
## Description Depends on #6522 Related FuelLabs/forc.pub#16 Adds a function, `get_bytecode_id` that generates a sha256 hash of the bytecode with the configurables section of the bytecode removed. This will be used for indexing and lookups of the corresponding ABIs for contracts, predicates, and scripts in the package registry (forc.pub). ## Checklist - [ ] I have linked to any relevant issues. - [ ] I have commented my code, particularly in hard-to-understand areas. - [ ] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [ ] If my change requires substantial documentation changes, I have [requested support from the DevRel team](https://github.com/FuelLabs/devrel-requests/issues/new/choose) - [ ] I have added tests that prove my fix is effective or that my feature works. - [ ] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [ ] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [ ] I have requested a review from the relevant team or maintainers. --------- Co-authored-by: Vaivaswatha Nagaraj <[email protected]> Co-authored-by: IGI-111 <[email protected]> Co-authored-by: Marcos Henrich <[email protected]> Co-authored-by: Joshua Batty <[email protected]>
- Loading branch information
1 parent
b823691
commit 6245ee6
Showing
10 changed files
with
177 additions
and
12 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
use anyhow::anyhow; | ||
use sha2::{Digest, Sha256}; | ||
use std::fs::File; | ||
use std::io::{BufReader, Read}; | ||
use std::path::Path; | ||
|
||
// The index of the beginning of the half-word (4 bytes) that contains the configurables section offset. | ||
const CONFIGURABLES_OFFSET_INSTR_LO: usize = 4; | ||
// The index of the end of the half-word (4 bytes) that contains the configurables section offset. | ||
const CONFIGURABLES_OFFSET_INSTR_HI: usize = 5; | ||
// The count of the beginning half-words that contain the configurables section offset. | ||
const CONFIGURABLES_OFFSET_PREAMBLE: usize = CONFIGURABLES_OFFSET_INSTR_HI + 1; | ||
|
||
/// A tuple of an instruction and its corresponding bytes. Useful when needing to access the raw bytes | ||
/// of an instruction that is parsed as [fuel_asm::InvalidOpcode], such as metadata in the preamble. | ||
pub type InstructionWithBytes = ( | ||
Result<fuel_asm::Instruction, fuel_asm::InvalidOpcode>, | ||
Vec<u8>, | ||
); | ||
|
||
/// An iterator over each [fuel_asm::Instruction] or [fuel_asm::InvalidOpcode] with its corresponding bytes. | ||
pub struct InstructionWithBytesIterator { | ||
buf_reader: BufReader<File>, | ||
} | ||
|
||
impl InstructionWithBytesIterator { | ||
/// Return a new iterator for each instruction parsed from raw bytes. | ||
pub fn new(buf_reader: BufReader<File>) -> Self { | ||
InstructionWithBytesIterator { buf_reader } | ||
} | ||
} | ||
|
||
impl Iterator for InstructionWithBytesIterator { | ||
type Item = InstructionWithBytes; | ||
|
||
fn next(&mut self) -> Option<InstructionWithBytes> { | ||
let mut buffer = [0; fuel_asm::Instruction::SIZE]; | ||
// Read the next instruction into the buffer | ||
match self.buf_reader.read_exact(&mut buffer) { | ||
Ok(_) => fuel_asm::from_bytes(buffer) | ||
.next() | ||
.map(|inst| (inst, buffer.to_vec())), | ||
Err(_) => None, | ||
} | ||
} | ||
} | ||
|
||
/// Parses a bytecode file into an iterator of instructions and their corresponding bytes. | ||
pub fn parse_bytecode_to_instructions<P>(path: P) -> anyhow::Result<InstructionWithBytesIterator> | ||
where | ||
P: AsRef<Path> + Clone, | ||
{ | ||
let f = File::open(path.clone()) | ||
.map_err(|_| anyhow!("{}: file not found", path.as_ref().to_string_lossy()))?; | ||
let buf_reader = BufReader::new(f); | ||
|
||
Ok(InstructionWithBytesIterator::new(buf_reader)) | ||
} | ||
|
||
/// Gets the bytecode ID from a bytecode file. The bytecode ID is the hash of the bytecode after removing the | ||
/// condigurables section, if any. | ||
pub fn get_bytecode_id<P>(path: P) -> anyhow::Result<String> | ||
where | ||
P: AsRef<Path> + Clone, | ||
{ | ||
let mut instructions = parse_bytecode_to_instructions(path.clone())?; | ||
|
||
// Collect the first six instructions into a temporary vector | ||
let mut first_six_instructions = Vec::with_capacity(CONFIGURABLES_OFFSET_PREAMBLE); | ||
for _ in 0..CONFIGURABLES_OFFSET_PREAMBLE { | ||
if let Some(instruction) = instructions.next() { | ||
first_six_instructions.push(instruction); | ||
} else { | ||
return Err(anyhow!("Incomplete bytecode")); | ||
} | ||
} | ||
|
||
let (lo_instr, low_raw) = &first_six_instructions[CONFIGURABLES_OFFSET_INSTR_LO]; | ||
let (hi_instr, hi_raw) = &first_six_instructions[CONFIGURABLES_OFFSET_INSTR_HI]; | ||
|
||
if let Err(fuel_asm::InvalidOpcode) = lo_instr { | ||
if let Err(fuel_asm::InvalidOpcode) = hi_instr { | ||
// Now assemble the configurables offset. | ||
let configurables_offset = usize::from_be_bytes([ | ||
low_raw[0], low_raw[1], low_raw[2], low_raw[3], hi_raw[0], hi_raw[1], hi_raw[2], | ||
hi_raw[3], | ||
]); | ||
|
||
// Hash the first six instructions | ||
let mut hasher = Sha256::new(); | ||
for (_, raw) in first_six_instructions { | ||
hasher.update(raw); | ||
} | ||
|
||
// Continue hashing the remaining instructions up to the configurables section offset. | ||
instructions | ||
.take( | ||
configurables_offset / fuel_asm::Instruction::SIZE | ||
- CONFIGURABLES_OFFSET_PREAMBLE, | ||
) // Minus 6 because we already hashed the first six | ||
.for_each(|(_, raw)| { | ||
hasher.update(raw); | ||
}); | ||
|
||
let hash_result = hasher.finalize(); | ||
let bytecode_id = format!("{:x}", hash_result); | ||
return Ok(bytecode_id); | ||
} | ||
} | ||
|
||
Err(anyhow!("Configurables section offset not found")) | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_get_bytecode_id_happy() { | ||
let bytecode_id: String = | ||
get_bytecode_id("tests/fixtures/bytecode/debug-counter.bin").expect("bytecode id"); | ||
assert_eq!( | ||
bytecode_id, | ||
"e65aa988cae1041b64dc2d85e496eed0e8a1d8105133bd313c17645a1859d53b".to_string() | ||
); | ||
|
||
let bytecode_id = | ||
get_bytecode_id("tests/fixtures/bytecode/release-counter.bin").expect("bytecode id"); | ||
assert_eq!( | ||
bytecode_id, | ||
"42ae8352cbc892d7c7621f1d6fb42b072a08ba5968508d49f54991668d4ea141".to_string() | ||
); | ||
|
||
let bytecode_id = | ||
get_bytecode_id("tests/fixtures/bytecode/debug-configurable_constants.bin") | ||
.expect("bytecode id"); | ||
assert_eq!( | ||
bytecode_id, | ||
"babc3d9dcac8d48dee1e5aeb3340ff098d3c1ab8b0a28341d9291d8ff757199e".to_string() | ||
); | ||
|
||
let bytecode_id = | ||
get_bytecode_id("tests/fixtures/bytecode/release-configurable_constants.bin") | ||
.expect("bytecode id"); | ||
assert_eq!( | ||
bytecode_id, | ||
"2adfb515b66763fd29391bdba012921d045a0be83d89be5492bcaacc429695e9".to_string() | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_get_bytecode_id_missing_configurable_offset() { | ||
let result = get_bytecode_id( | ||
"tests/fixtures/bytecode/debug-configurable_constants-missing-offset.bin", | ||
); | ||
assert_eq!( | ||
result.unwrap_err().to_string().as_str(), | ||
"Configurables section offset not found" | ||
); | ||
} | ||
|
||
#[test] | ||
fn test_get_bytecode_id_bad_path() { | ||
let result = get_bytecode_id("tests/fixtures/bytecode/blahblahblahblah.bin"); | ||
assert_eq!( | ||
result.unwrap_err().to_string().as_str(), | ||
"tests/fixtures/bytecode/blahblahblahblah.bin: file not found" | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+5.2 KB
forc-util/tests/fixtures/bytecode/debug-configurable_constants-missing-offset.bin
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters