Commit 7d603267 authored by clabby's avatar clabby

test: hydrated receipts

parent 0d752667
...@@ -5361,6 +5361,8 @@ name = "rethdb-reader" ...@@ -5361,6 +5361,8 @@ name = "rethdb-reader"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"reth", "reth",
"serde",
"serde_json",
] ]
[[package]] [[package]]
......
...@@ -10,3 +10,5 @@ crate-type = ["cdylib"] ...@@ -10,3 +10,5 @@ crate-type = ["cdylib"]
[dependencies] [dependencies]
reth = { git = "https://github.com/paradigmxyz/reth.git" } reth = { git = "https://github.com/paradigmxyz/reth.git" }
serde = "1.0.190"
serde_json = "1.0.107"
use reth::{ use reth::{
blockchain_tree::noop::NoopBlockchainTree, blockchain_tree::noop::NoopBlockchainTree,
primitives::{ primitives::{
alloy_primitives::private::alloy_rlp::Encodable, BlockHashOrNumber, ChainSpecBuilder, BlockHashOrNumber, ChainSpecBuilder, Receipt, TransactionMeta, TransactionSigned, U128,
U256, U64,
}, },
providers::{providers::BlockchainProvider, ProviderFactory, ReceiptProvider}, providers::{providers::BlockchainProvider, BlockReader, ProviderFactory, ReceiptProvider},
revm::primitives::calc_blob_gasprice,
rpc::types::{Log, TransactionReceipt},
utils::db::open_db_read_only, utils::db::open_db_read_only,
}; };
use std::{os::raw::c_char, path::Path, sync::Arc}; use std::{os::raw::c_char, path::Path, sync::Arc};
#[repr(C)]
pub struct ByteArray {
data: *mut u8,
len: usize,
}
#[repr(C)]
pub struct ByteArrays {
data: *mut ByteArray,
len: usize,
}
#[repr(C)] #[repr(C)]
pub struct ReceiptsResult { pub struct ReceiptsResult {
receipts: ByteArrays, data: *mut char,
data_len: usize,
error: bool, error: bool,
} }
// Implement a default for ByteArrays to be used in error cases
impl Default for ByteArrays {
fn default() -> Self {
ByteArrays {
data: std::ptr::null_mut(),
len: 0,
}
}
}
impl ReceiptsResult { impl ReceiptsResult {
pub fn success(receipts: ByteArrays) -> Self { pub fn success(data: *mut char, data_len: usize) -> Self {
Self { Self {
receipts, data,
data_len,
error: false, error: false,
} }
} }
pub fn fail() -> Self { pub fn fail() -> Self {
Self { Self {
receipts: ByteArrays::default(), data: std::ptr::null_mut(),
data_len: 0,
error: true, error: true,
} }
} }
...@@ -89,56 +73,147 @@ pub extern "C" fn read_receipts( ...@@ -89,56 +73,147 @@ pub extern "C" fn read_receipts(
return ReceiptsResult::fail(); return ReceiptsResult::fail();
}; };
let Ok(block) = provider.block_by_hash(block_hash.into()) else {
return ReceiptsResult::fail();
};
let Ok(receipts) = provider.receipts_by_block(BlockHashOrNumber::Hash(block_hash.into())) let Ok(receipts) = provider.receipts_by_block(BlockHashOrNumber::Hash(block_hash.into()))
else { else {
return ReceiptsResult::fail(); return ReceiptsResult::fail();
}; };
if let Some(receipts) = receipts { if let (Some(block), Some(receipts)) = (block, receipts) {
let receipts_rlp = receipts let block_number = block.number;
let base_fee = block.base_fee_per_gas;
let block_hash = block.hash_slow();
let excess_blob_gas = block.excess_blob_gas;
let Some(receipts) = block
.body
.into_iter() .into_iter()
.map(|r| { .zip(receipts.clone())
// todo - reduce alloc? .enumerate()
// RLP encode the receipt with a bloom filter. .map(|(idx, (tx, receipt))| {
let mut buf = Vec::default(); let meta = TransactionMeta {
r.with_bloom().encode(&mut buf); tx_hash: tx.hash,
index: idx as u64,
// Return a pointer to the `buf` and its length block_hash,
let res = ByteArray { block_number,
data: buf.as_mut_ptr(), base_fee,
len: buf.len(), excess_blob_gas,
}; };
build_transaction_receipt_with_block_receipts(tx, meta, receipt, &receipts)
// Forget the `buf` so that its memory isn't freed by the
// borrow checker at the end of this scope
std::mem::forget(buf);
res
}) })
.collect::<Vec<_>>(); .collect::<Option<Vec<_>>>()
else {
return ReceiptsResult::fail();
};
let result = ByteArrays { // Convert the receipts to JSON for transport
data: receipts_rlp.as_ptr() as *mut ByteArray, let Ok(mut receipts_json) = serde_json::to_string(&receipts) else {
len: receipts_rlp.len(), return ReceiptsResult::fail();
}; };
// Forget the `receipts_rlp` arr so that its memory isn't freed by the let res =
ReceiptsResult::success(receipts_json.as_mut_ptr() as *mut char, receipts_json.len());
// Forget the `receipts_json` string so that its memory isn't freed by the
// borrow checker at the end of this scope // borrow checker at the end of this scope
std::mem::forget(receipts_rlp); // Prevent Rust from freeing the memory std::mem::forget(receipts_json); // Prevent Rust from freeing the memory
ReceiptsResult::success(result) res
} else { } else {
return ReceiptsResult::fail(); ReceiptsResult::fail()
} }
} }
/// Free the [ByteArrays] data structure and its sub-components when they are no longer needed. /// Free a string that was allocated in Rust and passed to C.
#[no_mangle] #[no_mangle]
pub extern "C" fn free_byte_arrays(arrays: ByteArrays) { pub extern "C" fn free_string(string: *mut c_char) {
unsafe { unsafe {
let arrays = Vec::from_raw_parts(arrays.data, arrays.len, arrays.len); // Convert the raw pointer back to a CString and let it go out of scope,
for inner_array in arrays { // which will deallocate the memory.
let _ = Vec::from_raw_parts(inner_array.data, inner_array.len, inner_array.len); if !string.is_null() {
let _ = std::ffi::CString::from_raw(string);
}
}
}
pub(crate) fn build_transaction_receipt_with_block_receipts(
tx: TransactionSigned,
meta: TransactionMeta,
receipt: Receipt,
all_receipts: &[Receipt],
) -> Option<TransactionReceipt> {
let transaction = tx.clone().into_ecrecovered()?;
// get the previous transaction cumulative gas used
let gas_used = if meta.index == 0 {
receipt.cumulative_gas_used
} else {
let prev_tx_idx = (meta.index - 1) as usize;
all_receipts
.get(prev_tx_idx)
.map(|prev_receipt| receipt.cumulative_gas_used - prev_receipt.cumulative_gas_used)
.unwrap_or_default()
};
let mut res_receipt = TransactionReceipt {
transaction_hash: Some(meta.tx_hash),
transaction_index: U64::from(meta.index),
block_hash: Some(meta.block_hash),
block_number: Some(U256::from(meta.block_number)),
from: transaction.signer(),
to: None,
cumulative_gas_used: U256::from(receipt.cumulative_gas_used),
gas_used: Some(U256::from(gas_used)),
contract_address: None,
logs: Vec::with_capacity(receipt.logs.len()),
effective_gas_price: U128::from(transaction.effective_gas_price(meta.base_fee)),
transaction_type: tx.transaction.tx_type().into(),
// TODO pre-byzantium receipts have a post-transaction state root
state_root: None,
logs_bloom: receipt.bloom_slow(),
status_code: if receipt.success {
Some(U64::from(1))
} else {
Some(U64::from(0))
},
// EIP-4844 fields
blob_gas_price: meta.excess_blob_gas.map(calc_blob_gasprice).map(U128::from),
blob_gas_used: transaction.transaction.blob_gas_used().map(U128::from),
};
match tx.transaction.kind() {
reth::primitives::TransactionKind::Create => {
res_receipt.contract_address =
Some(transaction.signer().create(tx.transaction.nonce()));
}
reth::primitives::TransactionKind::Call(addr) => {
res_receipt.to = Some(*addr);
} }
} }
// get number of logs in the block
let mut num_logs = 0;
for prev_receipt in all_receipts.iter().take(meta.index as usize) {
num_logs += prev_receipt.logs.len();
}
for (tx_log_idx, log) in receipt.logs.into_iter().enumerate() {
let rpclog = Log {
address: log.address,
topics: log.topics,
data: log.data,
block_hash: Some(meta.block_hash),
block_number: Some(U256::from(meta.block_number)),
transaction_hash: Some(meta.tx_hash),
transaction_index: Some(U256::from(meta.index)),
log_index: Some(U256::from(num_logs + tx_log_idx)),
removed: false,
};
res_receipt.logs.push(rpclog);
}
Some(res_receipt)
} }
package sources package sources
import ( import (
"encoding/json"
"fmt" "fmt"
"unsafe"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
) )
...@@ -14,26 +17,15 @@ import ( ...@@ -14,26 +17,15 @@ import (
#include <stdbool.h> #include <stdbool.h>
typedef struct { typedef struct {
uint8_t* data; char* data;
size_t len; size_t data_len;
} ByteArray;
typedef struct {
ByteArray* data;
size_t len;
} ByteArrays;
// Define ReceiptsResult with a bool for error
typedef struct {
ByteArrays receipts;
bool error; bool error;
} ReceiptsResult; } ReceiptsResult;
extern ReceiptsResult read_receipts(const uint8_t* block_hash, size_t block_hash_len, const char* db_path); extern ReceiptsResult read_receipts(const uint8_t* block_hash, size_t block_hash_len, const char* db_path);
extern void free_byte_arrays(ByteArrays arrays); extern void free_string(char* string);
*/ */
import "C" import "C"
import "unsafe"
// FetchRethReceipts fetches the receipts for the given block hash directly from the Reth Database // FetchRethReceipts fetches the receipts for the given block hash directly from the Reth Database
// and populates the given results slice pointer with the receipts that were found. // and populates the given results slice pointer with the receipts that were found.
...@@ -51,23 +43,19 @@ func FetchRethReceipts(dbPath string, blockHash *common.Hash) (types.Receipts, e ...@@ -51,23 +43,19 @@ func FetchRethReceipts(dbPath string, blockHash *common.Hash) (types.Receipts, e
defer C.free(unsafe.Pointer(cDbPath)) defer C.free(unsafe.Pointer(cDbPath))
// Call the C function to fetch the receipts from the Reth Database // Call the C function to fetch the receipts from the Reth Database
byteArrayStruct := C.read_receipts((*C.uint8_t)(cBlockHash), C.size_t(len(blockHash)), cDbPath) receiptsResult := C.read_receipts((*C.uint8_t)(cBlockHash), C.size_t(len(blockHash)), cDbPath)
if byteArrayStruct.error { if receiptsResult.error {
return nil, fmt.Errorf("Error fetching receipts from Reth Database.") return nil, fmt.Errorf("Error fetching receipts from Reth Database.")
} }
// Convert the returned receipt RLP byte arrays to decoded Receipts. // Convert the returned JSON string to Go string and parse it
data := make(types.Receipts, byteArrayStruct.receipts.len) receiptsJSON := C.GoStringN(receiptsResult.data, C.int(receiptsResult.data_len))
byteArraySlice := (*[1 << 30]C.ByteArray)(unsafe.Pointer(byteArrayStruct.receipts.data))[:byteArrayStruct.receipts.len:byteArrayStruct.receipts.len] var receipts types.Receipts
for i, byteArray := range byteArraySlice { json.Unmarshal([]byte(receiptsJSON), &receipts)
receipt := types.Receipt{}
receipt.UnmarshalBinary(C.GoBytes(unsafe.Pointer(byteArray.data), C.int(byteArray.len)))
data[i] = &receipt
}
// Free the memory allocated by the C code // Free the memory allocated by the C code
C.free_byte_arrays(byteArrayStruct.receipts) C.free_string(receiptsResult.data)
return data, nil return receipts, nil
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment