-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
651 additions
and
0 deletions.
There are no files selected for viewing
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,5 @@ | ||
mod types; | ||
mod utils; | ||
|
||
pub use types::*; | ||
pub use utils::*; |
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,244 @@ | ||
use std::{ffi::CString, ops::Deref}; | ||
|
||
use ethers::{ | ||
core::k256::sha2::{Digest, Sha256}, | ||
types::{Address, Signature, H256, U256}, | ||
utils::keccak256, | ||
}; | ||
use lazy_static::lazy_static; | ||
use rlp::RlpStream; | ||
|
||
lazy_static! { | ||
static ref KZG_SETTINGS: c_kzg::KzgSettings = c_kzg::KzgSettings::load_trusted_setup_file( | ||
&CString::new("trusted_setup.txt").expect("C string"), | ||
) | ||
.unwrap(); | ||
} | ||
|
||
const BLOB_TX_TYPE: u8 = 0x03; | ||
const VERSIONED_HASH_VERSION_KZG: u8 = 1; | ||
const MAX_BLOBS_PER_BLOCK: usize = 6; | ||
pub const MAX_BYTES_PER_BLOB: usize = c_kzg::BYTES_PER_BLOB; | ||
|
||
pub trait BlobSigner { | ||
fn sign_hash(&self, hash: H256) -> std::result::Result<Signature, String>; | ||
} | ||
|
||
impl BlobSigner for ethers::signers::LocalWallet { | ||
fn sign_hash(&self, hash: H256) -> std::result::Result<Signature, String> { | ||
self.sign_hash(hash).map_err(|e| e.to_string()) | ||
} | ||
} | ||
|
||
pub struct PreparedBlob { | ||
pub commitment: Vec<u8>, | ||
pub proof: Vec<u8>, | ||
pub versioned_hash: H256, | ||
pub data: Vec<u8>, | ||
} | ||
|
||
pub struct BlobSidecar { | ||
blobs: Vec<PreparedBlob>, | ||
} | ||
|
||
impl BlobSidecar { | ||
pub fn new(data: Vec<u8>) -> std::result::Result<Self, String> { | ||
let num_blobs = data.len().div_ceil(MAX_BYTES_PER_BLOB); | ||
|
||
if num_blobs > MAX_BLOBS_PER_BLOCK { | ||
return Err(format!( | ||
"Data cannot fit into the maximum number of blobs per block: {}", | ||
MAX_BLOBS_PER_BLOCK | ||
)); | ||
} | ||
|
||
let blobs = Self::partition_data(data); | ||
let prepared_blobs = blobs.iter().map(|blob| Self::prepare_blob(blob)).collect(); | ||
|
||
Ok(Self { | ||
blobs: prepared_blobs, | ||
}) | ||
} | ||
|
||
pub fn num_blobs(&self) -> usize { | ||
self.blobs.len() | ||
} | ||
|
||
pub fn versioned_hashes(&self) -> Vec<H256> { | ||
self.blobs.iter().map(|blob| blob.versioned_hash).collect() | ||
} | ||
|
||
fn partition_data(data: Vec<u8>) -> Vec<c_kzg::Blob> { | ||
data.chunks(c_kzg::BYTES_PER_BLOB) | ||
.map(|chunk| { | ||
let mut blob = [0u8; c_kzg::BYTES_PER_BLOB]; | ||
blob[..c_kzg::BYTES_PER_BLOB].copy_from_slice(chunk); | ||
blob.into() | ||
}) | ||
.collect() | ||
} | ||
|
||
fn prepare_blob(blob: &c_kzg::Blob) -> PreparedBlob { | ||
let commitment = Self::kzg_commitment(blob); | ||
let versioned_hash = Self::commitment_to_versioned_hash(&commitment); | ||
let proof = Self::kzg_proof(blob, &commitment); | ||
|
||
PreparedBlob { | ||
commitment: commitment.to_vec(), | ||
proof: proof.to_vec(), | ||
versioned_hash, | ||
data: blob.to_vec(), | ||
} | ||
} | ||
|
||
fn kzg_commitment(blob: &c_kzg::Blob) -> c_kzg::KzgCommitment { | ||
c_kzg::KzgCommitment::blob_to_kzg_commitment(blob, &KZG_SETTINGS).unwrap() | ||
} | ||
|
||
fn commitment_to_versioned_hash(commitment: &c_kzg::KzgCommitment) -> H256 { | ||
let mut res: [u8; 32] = Sha256::digest(commitment.deref()).into(); | ||
res[0] = VERSIONED_HASH_VERSION_KZG; | ||
H256::from(res) | ||
} | ||
|
||
fn kzg_proof(blob: &c_kzg::Blob, commitment: &c_kzg::KzgCommitment) -> c_kzg::KzgProof { | ||
c_kzg::KzgProof::compute_blob_kzg_proof(blob, &commitment.to_bytes(), &KZG_SETTINGS) | ||
.unwrap() | ||
} | ||
} | ||
|
||
pub struct BlobTransaction { | ||
pub to: Address, | ||
pub chain_id: U256, | ||
pub gas_limit: U256, | ||
pub nonce: U256, | ||
pub max_fee_per_gas: U256, | ||
pub max_priority_fee_per_gas: U256, | ||
pub max_fee_per_blob_gas: U256, | ||
pub blob_versioned_hashes: Vec<H256>, | ||
} | ||
|
||
pub struct BlobTransactionEncoder { | ||
tx: BlobTransaction, | ||
sidecar: BlobSidecar, | ||
} | ||
|
||
impl BlobTransactionEncoder { | ||
pub fn new(tx: BlobTransaction, sidecar: BlobSidecar) -> Self { | ||
Self { tx, sidecar } | ||
} | ||
|
||
pub fn raw_signed_w_sidecar(self, signer: &impl BlobSigner) -> (H256, Vec<u8>) { | ||
let signed_tx_bytes = self.raw_signed(signer); | ||
let tx_hash = H256(keccak256(&signed_tx_bytes)); | ||
let final_bytes = self.encode_sidecar(signed_tx_bytes); | ||
|
||
(tx_hash, final_bytes) | ||
} | ||
|
||
fn encode_sidecar(self, payload: Vec<u8>) -> Vec<u8> { | ||
let blobs_count = self.sidecar.num_blobs(); | ||
|
||
let mut stream = RlpStream::new(); | ||
stream.begin_list(4); | ||
|
||
// skip the tx type byte | ||
stream.append_raw(&payload[1..], 1); | ||
|
||
let mut blob_stream = RlpStream::new_list(blobs_count); | ||
let mut commitment_stream = RlpStream::new_list(blobs_count); | ||
let mut proof_stream = RlpStream::new_list(blobs_count); | ||
|
||
for blob in self.sidecar.blobs { | ||
blob_stream.append(&blob.data); | ||
commitment_stream.append(&blob.commitment); | ||
proof_stream.append(&blob.proof); | ||
} | ||
|
||
stream.append_raw(&blob_stream.out(), 1); | ||
stream.append_raw(&commitment_stream.out(), 1); | ||
stream.append_raw(&proof_stream.out(), 1); | ||
|
||
let tx = [&[BLOB_TX_TYPE], stream.as_raw()].concat(); | ||
|
||
tx | ||
} | ||
|
||
fn raw_signed(&self, signer: &impl BlobSigner) -> Vec<u8> { | ||
let tx_bytes = self.encode(None); | ||
let signature = self.compute_signature(tx_bytes, signer); | ||
let signed_tx_bytes = self.encode(Some(signature)); | ||
|
||
signed_tx_bytes | ||
} | ||
|
||
fn compute_signature(&self, tx_bytes: Vec<u8>, signer: &impl BlobSigner) -> Signature { | ||
let message_hash = H256::from(keccak256(&tx_bytes)); | ||
let signature = signer | ||
.sign_hash(message_hash) | ||
.expect("signing should not fail"); | ||
|
||
signature | ||
} | ||
|
||
fn encode(&self, signature: Option<Signature>) -> Vec<u8> { | ||
let tx_bytes = if let Some(signature) = signature { | ||
self.rlp_signed(signature) | ||
} else { | ||
self.rlp() | ||
}; | ||
|
||
[&[BLOB_TX_TYPE], tx_bytes.as_slice()].concat() | ||
} | ||
|
||
fn rlp(&self) -> Vec<u8> { | ||
let mut stream = RlpStream::new(); | ||
stream.begin_list(11); | ||
|
||
self.append_common_tx_fields(&mut stream); | ||
Self::append_unused_fields(&mut stream); | ||
self.append_blob_tx_fields(&mut stream); | ||
|
||
stream.as_raw().to_vec() | ||
} | ||
|
||
fn rlp_signed(&self, signature: Signature) -> Vec<u8> { | ||
let mut stream = RlpStream::new(); | ||
stream.begin_list(14); | ||
|
||
self.append_common_tx_fields(&mut stream); | ||
Self::append_unused_fields(&mut stream); | ||
self.append_blob_tx_fields(&mut stream); | ||
|
||
self.append_signature(&mut stream, signature); | ||
|
||
stream.as_raw().to_vec() | ||
} | ||
|
||
fn append_common_tx_fields(&self, stream: &mut RlpStream) { | ||
stream.append(&self.tx.chain_id); | ||
stream.append(&self.tx.nonce); | ||
stream.append(&self.tx.max_priority_fee_per_gas); | ||
stream.append(&self.tx.max_fee_per_gas); | ||
stream.append(&self.tx.gas_limit); | ||
stream.append(&self.tx.to); | ||
} | ||
|
||
fn append_unused_fields(stream: &mut RlpStream) { | ||
// value, data and access_list | ||
stream.append_empty_data(); | ||
stream.append_empty_data(); | ||
stream.begin_list(0); | ||
} | ||
|
||
fn append_blob_tx_fields(&self, stream: &mut RlpStream) { | ||
stream.append(&self.tx.max_fee_per_blob_gas); | ||
stream.append_list(&self.tx.blob_versioned_hashes); | ||
} | ||
|
||
fn append_signature(&self, stream: &mut RlpStream, signature: Signature) { | ||
stream.append(&signature.v); | ||
stream.append(&signature.r); | ||
stream.append(&signature.s); | ||
} | ||
} |
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,35 @@ | ||
use ports::types::U256; | ||
|
||
const BLOB_BASE_FEE_UPDATE_FRACTION: u64 = 3338477; | ||
const GAS_PER_BLOB: u64 = 131_072; | ||
const MIN_BASE_FEE_PER_BLOB_GAS: u64 = 1; | ||
|
||
pub fn calculate_blob_fee(excess_blob_gas: U256, num_blobs: u64) -> U256 { | ||
get_total_blob_gas(num_blobs) * get_base_fee_per_blob_gas(excess_blob_gas) | ||
} | ||
|
||
fn get_total_blob_gas(num_blobs: u64) -> U256 { | ||
(GAS_PER_BLOB * num_blobs).into() | ||
} | ||
|
||
fn get_base_fee_per_blob_gas(excess_blob_gas: U256) -> U256 { | ||
fake_exponential( | ||
MIN_BASE_FEE_PER_BLOB_GAS.into(), | ||
excess_blob_gas, | ||
BLOB_BASE_FEE_UPDATE_FRACTION.into(), | ||
) | ||
} | ||
|
||
fn fake_exponential(factor: U256, numerator: U256, denominator: U256) -> U256 { | ||
assert!(!denominator.is_zero(), "attempt to divide by zero"); | ||
|
||
let mut i = 1; | ||
let mut output = U256::zero(); | ||
let mut numerator_accum = factor * denominator; | ||
while !numerator_accum.is_zero() { | ||
output = output + numerator_accum; | ||
numerator_accum = (numerator_accum * numerator) / (denominator * i); | ||
i += 1; | ||
} | ||
output / denominator | ||
} |
Oops, something went wrong.