Commit 0d752667 authored by clabby's avatar clabby

Add error handling in dylib

parent bc5e060e
...@@ -20,6 +20,38 @@ pub struct ByteArrays { ...@@ -20,6 +20,38 @@ pub struct ByteArrays {
len: usize, len: usize,
} }
#[repr(C)]
pub struct ReceiptsResult {
receipts: ByteArrays,
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 {
pub fn success(receipts: ByteArrays) -> Self {
Self {
receipts,
error: false,
}
}
pub fn fail() -> Self {
Self {
receipts: ByteArrays::default(),
error: true,
}
}
}
/// Read the receipts for a blockhash from the RETH database directly. /// Read the receipts for a blockhash from the RETH database directly.
/// ///
/// WARNING: Will panic on error. /// WARNING: Will panic on error.
...@@ -29,72 +61,82 @@ pub extern "C" fn read_receipts( ...@@ -29,72 +61,82 @@ pub extern "C" fn read_receipts(
block_hash: *const u8, block_hash: *const u8,
block_hash_len: usize, block_hash_len: usize,
db_path: *const c_char, db_path: *const c_char,
) -> ByteArrays { ) -> ReceiptsResult {
// Convert the raw pointer and length back to a Rust slice // Convert the raw pointer and length back to a Rust slice
let block_hash: [u8; 32] = unsafe { std::slice::from_raw_parts(block_hash, block_hash_len) } let Ok(block_hash): Result<[u8; 32], _> =
.try_into() unsafe { std::slice::from_raw_parts(block_hash, block_hash_len) }.try_into()
.expect("Block hash must be 32 bytes long"); else {
return ReceiptsResult::fail();
};
// Convert the *const c_char to a Rust &str // Convert the *const c_char to a Rust &str
let db_path_str = unsafe { let Ok(db_path_str) = unsafe {
assert!(!db_path.is_null(), "Null pointer for database path"); assert!(!db_path.is_null(), "Null pointer for database path");
std::ffi::CStr::from_ptr(db_path) std::ffi::CStr::from_ptr(db_path)
.to_str() }
.expect("Invalid UTF-8 for database path") .to_str() else {
return ReceiptsResult::fail();
}; };
let db = open_db_read_only(&Path::new(db_path_str), None).expect("Could not open reth DB"); let Ok(db) = open_db_read_only(&Path::new(db_path_str), None) else {
return ReceiptsResult::fail();
};
let spec = Arc::new(ChainSpecBuilder::mainnet().build()); let spec = Arc::new(ChainSpecBuilder::mainnet().build());
let factory = ProviderFactory::new(db, spec.clone()); let factory = ProviderFactory::new(db, spec.clone());
// Create a read-only BlockChainProvider // Create a read-only BlockChainProvider
let provider = BlockchainProvider::new(factory, NoopBlockchainTree::default()) let Ok(provider) = BlockchainProvider::new(factory, NoopBlockchainTree::default()) else {
.expect("Failed to create blockchain provider."); return ReceiptsResult::fail();
let receipts = provider };
.receipts_by_block(BlockHashOrNumber::Hash(block_hash.into()))
.expect("Could not fetch receipts for block") let Ok(receipts) = provider.receipts_by_block(BlockHashOrNumber::Hash(block_hash.into()))
.expect("No receipts found for block"); else {
return ReceiptsResult::fail();
// Serialize receipts to RLP for the FFI interface.
let receipts_rlp = receipts
.into_iter()
.map(|r| {
// todo - reduce alloc?
// RLP encode the receipt with a bloom filter.
let mut buf = Vec::default();
r.with_bloom().encode(&mut buf);
// Return a pointer to the `buf` and its length
let res = ByteArray {
data: buf.as_mut_ptr(),
len: buf.len(),
};
// 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<_>>();
let result = ByteArrays {
data: receipts_rlp.as_ptr() as *mut ByteArray,
len: receipts_rlp.len(),
}; };
// Forget the `receipts_rlp` arr so that its memory isn't freed by the if let Some(receipts) = receipts {
// borrow checker at the end of this scope let receipts_rlp = receipts
std::mem::forget(receipts_rlp); // Prevent Rust from freeing the memory .into_iter()
.map(|r| {
// todo - reduce alloc?
// RLP encode the receipt with a bloom filter.
let mut buf = Vec::default();
r.with_bloom().encode(&mut buf);
result // Return a pointer to the `buf` and its length
let res = ByteArray {
data: buf.as_mut_ptr(),
len: buf.len(),
};
// 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<_>>();
let result = ByteArrays {
data: receipts_rlp.as_ptr() as *mut ByteArray,
len: receipts_rlp.len(),
};
// Forget the `receipts_rlp` arr so that its memory isn't freed by the
// borrow checker at the end of this scope
std::mem::forget(receipts_rlp); // Prevent Rust from freeing the memory
ReceiptsResult::success(result)
} else {
return ReceiptsResult::fail();
}
} }
/// Free the [ByteArrays] data structure and its sub-components when they are no longer needed. /// Free the [ByteArrays] data structure and its sub-components when they are no longer needed.
#[no_mangle] #[no_mangle]
pub extern "C" fn free_byte_arrays(array: ByteArrays) { pub extern "C" fn free_byte_arrays(arrays: ByteArrays) {
unsafe { unsafe {
let arrays = Vec::from_raw_parts(array.data, array.len, array.len); let arrays = Vec::from_raw_parts(arrays.data, arrays.len, arrays.len);
for inner_array in arrays { for inner_array in arrays {
let _ = Vec::from_raw_parts(inner_array.data, inner_array.len, inner_array.len); let _ = Vec::from_raw_parts(inner_array.data, inner_array.len, inner_array.len);
} }
......
...@@ -124,7 +124,7 @@ const ( ...@@ -124,7 +124,7 @@ const (
RPCKindBasic RPCProviderKind = "basic" // try only the standard most basic receipt fetching RPCKindBasic RPCProviderKind = "basic" // try only the standard most basic receipt fetching
RPCKindAny RPCProviderKind = "any" // try any method available RPCKindAny RPCProviderKind = "any" // try any method available
RPCKindStandard RPCProviderKind = "standard" // try standard methods, including newer optimized standard RPC methods RPCKindStandard RPCProviderKind = "standard" // try standard methods, including newer optimized standard RPC methods
RPCKindRethDB RPCProviderKind = "reth_db" // read data directly from reth's MDBX database RPCKindRethDB RPCProviderKind = "reth_db" // read data directly from reth's database
) )
var RPCProviderKinds = []RPCProviderKind{ var RPCProviderKinds = []RPCProviderKind{
...@@ -311,7 +311,7 @@ func AvailableReceiptsFetchingMethods(kind RPCProviderKind) ReceiptsFetchingMeth ...@@ -311,7 +311,7 @@ func AvailableReceiptsFetchingMethods(kind RPCProviderKind) ReceiptsFetchingMeth
case RPCKindBasic: case RPCKindBasic:
return EthGetTransactionReceiptBatch return EthGetTransactionReceiptBatch
case RPCKindAny: case RPCKindAny:
// if it's any kind of RPC provider, then try all methods (except for RethGetBlockReceiptsMDBX) // if it's any kind of RPC provider, then try all methods (except for RethGetBlockReceipts)
return AlchemyGetTransactionReceipts | EthGetBlockReceipts | return AlchemyGetTransactionReceipts | EthGetBlockReceipts |
DebugGetRawReceipts | ErigonGetBlockReceiptsByBlockHash | DebugGetRawReceipts | ErigonGetBlockReceiptsByBlockHash |
ParityGetBlockReceipts | EthGetTransactionReceiptBatch ParityGetBlockReceipts | EthGetTransactionReceiptBatch
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
#cgo LDFLAGS: -L../rethdb-reader/target/release -lrethdbreader #cgo LDFLAGS: -L../rethdb-reader/target/release -lrethdbreader
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
typedef struct { typedef struct {
uint8_t* data; uint8_t* data;
...@@ -22,7 +23,13 @@ typedef struct { ...@@ -22,7 +23,13 @@ typedef struct {
size_t len; size_t len;
} ByteArrays; } ByteArrays;
extern ByteArrays read_receipts(const uint8_t* block_hash, size_t block_hash_len, const char* db_path); // Define ReceiptsResult with a bool for error
typedef struct {
ByteArrays receipts;
bool error;
} ReceiptsResult;
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_byte_arrays(ByteArrays arrays);
*/ */
import "C" import "C"
...@@ -46,9 +53,13 @@ func FetchRethReceipts(dbPath string, blockHash *common.Hash) (types.Receipts, e ...@@ -46,9 +53,13 @@ func FetchRethReceipts(dbPath string, blockHash *common.Hash) (types.Receipts, e
// 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) byteArrayStruct := C.read_receipts((*C.uint8_t)(cBlockHash), C.size_t(len(blockHash)), cDbPath)
if byteArrayStruct.error {
return nil, fmt.Errorf("Error fetching receipts from Reth Database.")
}
// Convert the returned receipt RLP byte arrays to decoded Receipts. // Convert the returned receipt RLP byte arrays to decoded Receipts.
data := make(types.Receipts, byteArrayStruct.len) data := make(types.Receipts, byteArrayStruct.receipts.len)
byteArraySlice := (*[1 << 30]C.ByteArray)(unsafe.Pointer(byteArrayStruct.data))[:byteArrayStruct.len:byteArrayStruct.len] byteArraySlice := (*[1 << 30]C.ByteArray)(unsafe.Pointer(byteArrayStruct.receipts.data))[:byteArrayStruct.receipts.len:byteArrayStruct.receipts.len]
for i, byteArray := range byteArraySlice { for i, byteArray := range byteArraySlice {
receipt := types.Receipt{} receipt := types.Receipt{}
receipt.UnmarshalBinary(C.GoBytes(unsafe.Pointer(byteArray.data), C.int(byteArray.len))) receipt.UnmarshalBinary(C.GoBytes(unsafe.Pointer(byteArray.data), C.int(byteArray.len)))
...@@ -56,7 +67,7 @@ func FetchRethReceipts(dbPath string, blockHash *common.Hash) (types.Receipts, e ...@@ -56,7 +67,7 @@ func FetchRethReceipts(dbPath string, blockHash *common.Hash) (types.Receipts, e
} }
// Free the memory allocated by the C code // Free the memory allocated by the C code
C.free_byte_arrays(byteArrayStruct) C.free_byte_arrays(byteArrayStruct.receipts)
return data, nil return data, 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