Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
679eb237
Commit
679eb237
authored
Oct 27, 2023
by
clabby
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
:broom: dylib
parent
e9a8f81a
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
305 additions
and
251 deletions
+305
-251
.gitignore
op-service/rethdb-reader/.gitignore
+3
-0
Cargo.lock
op-service/rethdb-reader/Cargo.lock
+1
-0
Cargo.toml
op-service/rethdb-reader/Cargo.toml
+1
-0
README.md
op-service/rethdb-reader/README.md
+69
-25
headgen.sh
op-service/rethdb-reader/headgen.sh
+16
-0
lib.rs
op-service/rethdb-reader/src/lib.rs
+4
-210
receipts.rs
op-service/rethdb-reader/src/receipts.rs
+211
-0
reth_db_test.go
op-service/sources/reth_db_test.go
+0
-16
No files found.
op-service/rethdb-reader/.gitignore
View file @
679eb237
# Target
target/
# Bindings
rdb.h
op-service/rethdb-reader/Cargo.lock
View file @
679eb237
...
...
@@ -5360,6 +5360,7 @@ dependencies = [
name = "rethdb-reader"
version = "0.1.0"
dependencies = [
"anyhow",
"reth",
"serde",
"serde_json",
...
...
op-service/rethdb-reader/Cargo.toml
View file @
679eb237
...
...
@@ -12,3 +12,4 @@ crate-type = ["cdylib"]
reth
=
{
git
=
"https://github.com/paradigmxyz/reth.git"
}
serde
=
"1.0.190"
serde_json
=
"1.0.107"
anyhow
=
"1.0.75"
op-service/rethdb-reader/README.md
View file @
679eb237
...
...
@@ -3,37 +3,81 @@
A dylib to be accessed via FFI in
`op-service`
's
`sources`
package for reading information
directly from the
`reth`
database.
## Developing
**Building**
To build the dylib, you must first have the
[
Rust Toolchain
][
rust-toolchain
]
installed.
```
sh
cargo build
--release
```
**Docs**
Documentation is available via rustdoc.
```
sh
cargo doc
--open
```
**Linting**
```
sh
cargo +nightly
fmt
--
&&
cargo +nightly clippy
--all
--all-features
--
-D
warnings
```
**Generating the C header**
To generate the C header, first install
`cbindgen`
via
`cargo install cbindgen --force`
. Then, run the generation script:
```
sh
./headgen.sh
```
### C Header
The C header below is generated by
`cbindgen`
, and it is the interface that consumers of the dylib use to call its exported
functions. Currently, the only exported functions pertain to reading fully hydrated block receipts from the database.
```
c
#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
struct
ReceiptsResult
{
/**
* A [ReceiptsResult] is a wrapper around a JSON string containing serialized [TransactionReceipt]s
* as well as an error status that is compatible with FFI.
*
* # Safety
* - When the `error` field is false, the `data` pointer is guaranteed to be valid.
* - When the `error` field is true, the `data` pointer is guaranteed to be null.
*/
typedef
struct
ReceiptsResult
{
uint32_t
*
data
;
uintptr_t
data_len
;
bool
error
;
};
extern
"C"
{
}
ReceiptsResult
;
/// Read the receipts for a blockhash from the RETH database directly.
///
/// # Safety
/// - All possible nil pointer dereferences are checked, and the function will return a
/// failing [ReceiptsResult] if any are found.
ReceiptsResult
read_receipts
(
const
uint8_t
*
block_hash
,
/**
* Read the receipts for a blockhash from the RETH database directly.
*
* # Safety
* - All possible nil pointer dereferences are checked, and the function will return a
* failing [ReceiptsResult] if any are found.
*/
struct
ReceiptsResult
read_receipts
(
const
uint8_t
*
block_hash
,
uintptr_t
block_hash_len
,
const
char
*
db_path
);
/// Free a string that was allocated in Rust and passed to C.
///
/// # Safety
/// - All possible nil pointer dereferences are checked.
/**
* Free a string that was allocated in Rust and passed to C.
*
* # Safety
* - All possible nil pointer dereferences are checked.
*/
void
free_string
(
char
*
string
);
}
```
[
rust-toolchain
]:
https://rustup.rs/
op-service/rethdb-reader/headgen.sh
0 → 100755
View file @
679eb237
#!/bin/bash
set
-e
# Generate rdb.h
cbindgen
--crate
rethdb-reader
--output
rdb.h
-l
C
# Process README.md to replace the content within the specified code block
awk
'
BEGIN { in_code_block=0; }
/^```c/ { in_code_block=1; print; next; }
/^```/ && in_code_block { in_code_block=0; while ((getline line < "rdb.h") > 0) print line; }
!in_code_block { print; }
'
README.md
>
README.tmp
&&
mv
README.tmp README.md
echo
"Generated C header successfully"
op-service/rethdb-reader/src/lib.rs
View file @
679eb237
#![doc
=
include_str
!
(
"../README.md"
)]
use
reth
::{
blockchain_tree
::
noop
::
NoopBlockchainTree
,
primitives
::{
BlockHashOrNumber
,
Receipt
,
TransactionKind
,
TransactionMeta
,
TransactionSigned
,
MAINNET
,
U128
,
U256
,
U64
,
},
providers
::{
providers
::
BlockchainProvider
,
BlockReader
,
ProviderFactory
,
ReceiptProvider
},
rpc
::
types
::{
Log
,
TransactionReceipt
},
utils
::
db
::
open_db_read_only
,
};
use
std
::{
os
::
raw
::
c_char
,
path
::
Path
};
use
receipts
::{
read_receipts_inner
,
ReceiptsResult
};
use
std
::
os
::
raw
::
c_char
;
/// A [ReceiptsResult] is a wrapper around a JSON string containing serialized [TransactionReceipt]s
/// as well as an error status that is compatible with FFI.
///
/// # Safety
/// - When the `error` field is false, the `data` pointer is guaranteed to be valid.
/// - When the `error` field is true, the `data` pointer is guaranteed to be null.
#[repr(C)]
pub
struct
ReceiptsResult
{
data
:
*
mut
char
,
data_len
:
usize
,
error
:
bool
,
}
impl
ReceiptsResult
{
/// Constructs a successful [ReceiptsResult] from a JSON string.
pub
fn
success
(
data
:
*
mut
char
,
data_len
:
usize
)
->
Self
{
Self
{
data
,
data_len
,
error
:
false
,
}
}
/// Constructs a failing [ReceiptsResult] with a null pointer to the data.
pub
fn
fail
()
->
Self
{
Self
{
data
:
std
::
ptr
::
null_mut
(),
data_len
:
0
,
error
:
true
,
}
}
}
mod
receipts
;
/// Read the receipts for a blockhash from the RETH database directly.
///
...
...
@@ -56,88 +16,7 @@ pub unsafe extern "C" fn read_receipts(
block_hash_len
:
usize
,
db_path
:
*
const
c_char
,
)
->
ReceiptsResult
{
// Convert the raw pointer and length back to a Rust slice
let
Ok
(
block_hash
):
Result
<
[
u8
;
32
],
_
>
=
{
if
block_hash
.is_null
()
{
return
ReceiptsResult
::
fail
();
}
std
::
slice
::
from_raw_parts
(
block_hash
,
block_hash_len
)
}
.try_into
()
else
{
return
ReceiptsResult
::
fail
();
};
// Convert the *const c_char to a Rust &str
let
Ok
(
db_path_str
)
=
{
if
db_path
.is_null
()
{
return
ReceiptsResult
::
fail
();
}
std
::
ffi
::
CStr
::
from_ptr
(
db_path
)
}
.to_str
()
else
{
return
ReceiptsResult
::
fail
();
};
let
Ok
(
db
)
=
open_db_read_only
(
Path
::
new
(
db_path_str
),
None
)
else
{
return
ReceiptsResult
::
fail
();
};
let
factory
=
ProviderFactory
::
new
(
db
,
MAINNET
.clone
());
// Create a read-only BlockChainProvider
let
Ok
(
provider
)
=
BlockchainProvider
::
new
(
factory
,
NoopBlockchainTree
::
default
())
else
{
return
ReceiptsResult
::
fail
();
};
// Fetch the block and the receipts within it
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
()))
else
{
return
ReceiptsResult
::
fail
();
};
if
let
(
Some
(
block
),
Some
(
receipts
))
=
(
block
,
receipts
)
{
let
block_number
=
block
.number
;
let
base_fee
=
block
.base_fee_per_gas
;
let
block_hash
=
block
.hash_slow
();
let
Some
(
receipts
)
=
block
.body
.into_iter
()
.zip
(
receipts
.clone
())
.enumerate
()
.map
(|(
idx
,
(
tx
,
receipt
))|
{
let
meta
=
TransactionMeta
{
tx_hash
:
tx
.hash
,
index
:
idx
as
u64
,
block_hash
,
block_number
,
base_fee
,
excess_blob_gas
:
None
,
};
build_transaction_receipt_with_block_receipts
(
tx
,
meta
,
receipt
,
&
receipts
)
})
.collect
::
<
Option
<
Vec
<
_
>>>
()
else
{
return
ReceiptsResult
::
fail
();
};
// Convert the receipts to JSON for transport
let
Ok
(
mut
receipts_json
)
=
serde_json
::
to_string
(
&
receipts
)
else
{
return
ReceiptsResult
::
fail
();
};
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
std
::
mem
::
forget
(
receipts_json
);
// Prevent Rust from freeing the memory
res
}
else
{
ReceiptsResult
::
fail
()
}
read_receipts_inner
(
block_hash
,
block_hash_len
,
db_path
)
.unwrap_or
(
ReceiptsResult
::
fail
())
}
/// Free a string that was allocated in Rust and passed to C.
...
...
@@ -152,88 +31,3 @@ pub unsafe extern "C" fn free_string(string: *mut c_char) {
let
_
=
std
::
ffi
::
CString
::
from_raw
(
string
);
}
}
/// Builds a hydrated [TransactionReceipt] from information in the passed transaction,
/// receipt, and block receipts.
///
/// Returns [None] if the transaction's sender could not be recovered from the signature.
#[inline(always)]
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
:
None
,
blob_gas_used
:
None
,
};
match
tx
.transaction
.kind
()
{
TransactionKind
::
Create
=>
{
res_receipt
.contract_address
=
Some
(
transaction
.signer
()
.create
(
tx
.transaction
.nonce
()));
}
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
)
}
op-service/rethdb-reader/src/receipts.rs
0 → 100644
View file @
679eb237
//! This module contains the logic for reading a block's fully hydrated receipts directly from the
//! [reth] database.
use
anyhow
::{
anyhow
,
Result
};
use
reth
::{
blockchain_tree
::
noop
::
NoopBlockchainTree
,
primitives
::{
BlockHashOrNumber
,
Receipt
,
TransactionKind
,
TransactionMeta
,
TransactionSigned
,
MAINNET
,
U128
,
U256
,
U64
,
},
providers
::{
providers
::
BlockchainProvider
,
BlockReader
,
ProviderFactory
,
ReceiptProvider
},
rpc
::
types
::{
Log
,
TransactionReceipt
},
utils
::
db
::
open_db_read_only
,
};
use
std
::{
ffi
::
c_char
,
path
::
Path
};
/// A [ReceiptsResult] is a wrapper around a JSON string containing serialized [TransactionReceipt]s
/// as well as an error status that is compatible with FFI.
///
/// # Safety
/// - When the `error` field is false, the `data` pointer is guaranteed to be valid.
/// - When the `error` field is true, the `data` pointer is guaranteed to be null.
#[repr(C)]
pub
struct
ReceiptsResult
{
data
:
*
mut
char
,
data_len
:
usize
,
error
:
bool
,
}
impl
ReceiptsResult
{
/// Constructs a successful [ReceiptsResult] from a JSON string.
pub
fn
success
(
data
:
*
mut
char
,
data_len
:
usize
)
->
Self
{
Self
{
data
,
data_len
,
error
:
false
,
}
}
/// Constructs a failing [ReceiptsResult] with a null pointer to the data.
pub
fn
fail
()
->
Self
{
Self
{
data
:
std
::
ptr
::
null_mut
(),
data_len
:
0
,
error
:
true
,
}
}
}
/// Read the receipts for a blockhash from the RETH database directly.
///
/// # Safety
/// - All possible nil pointer dereferences are checked, and the function will return a
/// failing [ReceiptsResult] if any are found.
#[inline(always)]
pub
(
crate
)
unsafe
fn
read_receipts_inner
(
block_hash
:
*
const
u8
,
block_hash_len
:
usize
,
db_path
:
*
const
c_char
,
)
->
Result
<
ReceiptsResult
>
{
// Convert the raw pointer and length back to a Rust slice
let
block_hash
:
[
u8
;
32
]
=
{
if
block_hash
.is_null
()
{
anyhow
::
bail!
(
"block_hash pointer is null"
);
}
std
::
slice
::
from_raw_parts
(
block_hash
,
block_hash_len
)
}
.try_into
()
?
;
// Convert the *const c_char to a Rust &str
let
db_path_str
=
{
if
db_path
.is_null
()
{
anyhow
::
bail!
(
"db path pointer is null"
);
}
std
::
ffi
::
CStr
::
from_ptr
(
db_path
)
}
.to_str
()
?
;
let
db
=
open_db_read_only
(
Path
::
new
(
db_path_str
),
None
)
.map_err
(|
e
|
anyhow!
(
e
))
?
;
let
factory
=
ProviderFactory
::
new
(
db
,
MAINNET
.clone
());
// Create a read-only BlockChainProvider
let
provider
=
BlockchainProvider
::
new
(
factory
,
NoopBlockchainTree
::
default
())
?
;
// Fetch the block and the receipts within it
let
block
=
provider
.block_by_hash
(
block_hash
.into
())
?
.ok_or
(
anyhow!
(
"Failed to fetch block"
))
?
;
let
receipts
=
provider
.receipts_by_block
(
BlockHashOrNumber
::
Hash
(
block_hash
.into
()))
?
.ok_or
(
anyhow!
(
"Failed to fetch block receipts"
))
?
;
let
block_number
=
block
.number
;
let
base_fee
=
block
.base_fee_per_gas
;
let
block_hash
=
block
.hash_slow
();
let
receipts
=
block
.body
.into_iter
()
.zip
(
receipts
.clone
())
.enumerate
()
.map
(|(
idx
,
(
tx
,
receipt
))|
{
let
meta
=
TransactionMeta
{
tx_hash
:
tx
.hash
,
index
:
idx
as
u64
,
block_hash
,
block_number
,
base_fee
,
excess_blob_gas
:
None
,
};
build_transaction_receipt_with_block_receipts
(
tx
,
meta
,
receipt
,
&
receipts
)
})
.collect
::
<
Option
<
Vec
<
_
>>>
()
.ok_or
(
anyhow!
(
"Failed to build receipts"
))
?
;
// Convert the receipts to JSON for transport
let
mut
receipts_json
=
serde_json
::
to_string
(
&
receipts
)
?
;
// Create a ReceiptsResult with a pointer to the json-ified receipts
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
std
::
mem
::
forget
(
receipts_json
);
// Prevent Rust from freeing the memory
Ok
(
res
)
}
/// Builds a hydrated [TransactionReceipt] from information in the passed transaction,
/// receipt, and block receipts.
///
/// Returns [None] if the transaction's sender could not be recovered from the signature.
#[inline(always)]
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
:
None
,
blob_gas_used
:
None
,
};
match
tx
.transaction
.kind
()
{
TransactionKind
::
Create
=>
{
res_receipt
.contract_address
=
Some
(
transaction
.signer
()
.create
(
tx
.transaction
.nonce
()));
}
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
)
}
op-service/sources/reth_db_test.go
deleted
100644 → 0
View file @
e9a8f81a
package
sources
import
(
"testing"
"github.com/ethereum/go-ethereum/common"
)
func
TestRethDBRead
(
t
*
testing
.
T
)
{
t
.
Parallel
()
_
,
err
:=
FetchRethReceipts
(
"/test"
,
&
common
.
Hash
{})
if
err
!=
nil
{
panic
(
"test"
)
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment