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
80d619ab
Commit
80d619ab
authored
Oct 26, 2023
by
clabby
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
PoC init
parent
48c735e9
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
7679 additions
and
2 deletions
+7679
-2
.gitignore
op-service/rethdb-reader/.gitignore
+2
-0
Cargo.lock
op-service/rethdb-reader/Cargo.lock
+7449
-0
Cargo.toml
op-service/rethdb-reader/Cargo.toml
+12
-0
README.md
op-service/rethdb-reader/README.md
+4
-0
lib.rs
op-service/rethdb-reader/src/lib.rs
+102
-0
receipts.go
op-service/sources/receipts.go
+26
-2
reth_db.go
op-service/sources/reth_db.go
+62
-0
reth_db_test.go
op-service/sources/reth_db_test.go
+22
-0
No files found.
op-service/rethdb-reader/.gitignore
0 → 100644
View file @
80d619ab
# Target
target/
op-service/rethdb-reader/Cargo.lock
0 → 100644
View file @
80d619ab
This diff is collapsed.
Click to expand it.
op-service/rethdb-reader/Cargo.toml
0 → 100644
View file @
80d619ab
[package]
name
=
"rethdb-reader"
description
=
"A simple library for reading data through Reth's DB abstractions."
version
=
"0.1.0"
edition
=
"2021"
[lib]
name
=
"rethdbreader"
crate-type
=
["cdylib"]
[dependencies]
reth
=
{
git
=
"https://github.com/paradigmxyz/reth.git"
}
op-service/rethdb-reader/README.md
0 → 100644
View file @
80d619ab
# `rethdb-reader`
Exported Rust code to be used via FFI in
`op-service`
's
`sources`
package for reading information
directly from the
`reth`
database.
op-service/rethdb-reader/src/lib.rs
0 → 100644
View file @
80d619ab
use
reth
::{
blockchain_tree
::
noop
::
NoopBlockchainTree
,
primitives
::{
alloy_primitives
::
private
::
alloy_rlp
::
Encodable
,
BlockHashOrNumber
,
ChainSpecBuilder
,
},
providers
::{
providers
::
BlockchainProvider
,
ProviderFactory
,
ReceiptProvider
},
utils
::
db
::
open_db_read_only
,
};
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
,
}
/// Read the receipts for a blockhash from the RETH database directly.
///
/// WARNING: Will panic on error.
/// TODO: Gracefully return OK status.
#[no_mangle]
pub
extern
"C"
fn
read_receipts
(
block_hash
:
*
const
u8
,
block_hash_len
:
usize
,
db_path
:
*
const
c_char
,
)
->
ByteArrays
{
// 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
)
}
.try_into
()
.expect
(
"Block hash must be 32 bytes long"
);
// Convert the *const c_char to a Rust &str
let
db_path_str
=
unsafe
{
assert
!
(
!
db_path
.is_null
(),
"Null pointer for database path"
);
std
::
ffi
::
CStr
::
from_ptr
(
db_path
)
.to_str
()
.expect
(
"Invalid UTF-8 for database path"
)
};
let
db
=
open_db_read_only
(
&
Path
::
new
(
db_path_str
),
None
)
.expect
(
"Could not open reth DB"
);
let
spec
=
Arc
::
new
(
ChainSpecBuilder
::
mainnet
()
.build
());
let
factory
=
ProviderFactory
::
new
(
db
,
spec
.clone
());
// Create a read-only BlockChainProvider
let
provider
=
BlockchainProvider
::
new
(
factory
,
NoopBlockchainTree
::
default
())
.expect
(
"Failed to create blockchain provider."
);
let
receipts
=
provider
.receipts_by_block
(
BlockHashOrNumber
::
Hash
(
block_hash
.into
()))
.expect
(
"Could not fetch receipts for block"
)
.expect
(
"No receipts found for block"
);
// 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
// borrow checker at the end of this scope
std
::
mem
::
forget
(
receipts_rlp
);
// Prevent Rust from freeing the memory
result
}
/// Free the [ByteArrays] data structure and its sub-components when they are no longer needed.
#[no_mangle]
pub
extern
"C"
fn
free_byte_arrays
(
array
:
ByteArrays
)
{
unsafe
{
let
arrays
=
Vec
::
from_raw_parts
(
array
.data
,
array
.len
,
array
.len
);
for
inner_array
in
arrays
{
let
_
=
Vec
::
from_raw_parts
(
inner_array
.data
,
inner_array
.len
,
inner_array
.len
);
}
}
}
op-service/sources/receipts.go
View file @
80d619ab
...
@@ -124,6 +124,7 @@ const (
...
@@ -124,6 +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
)
)
var
RPCProviderKinds
=
[]
RPCProviderKind
{
var
RPCProviderKinds
=
[]
RPCProviderKind
{
...
@@ -137,6 +138,7 @@ var RPCProviderKinds = []RPCProviderKind{
...
@@ -137,6 +138,7 @@ var RPCProviderKinds = []RPCProviderKind{
RPCKindBasic
,
RPCKindBasic
,
RPCKindAny
,
RPCKindAny
,
RPCKindStandard
,
RPCKindStandard
,
RPCKindRethDB
,
}
}
func
(
kind
RPCProviderKind
)
String
()
string
{
func
(
kind
RPCProviderKind
)
String
()
string
{
...
@@ -268,6 +270,18 @@ const (
...
@@ -268,6 +270,18 @@ const (
// See:
// See:
// https://github.com/ledgerwatch/erigon/blob/287a3d1d6c90fc6a7a088b5ae320f93600d5a167/cmd/rpcdaemon/commands/erigon_receipts.go#LL391C24-L391C51
// https://github.com/ledgerwatch/erigon/blob/287a3d1d6c90fc6a7a088b5ae320f93600d5a167/cmd/rpcdaemon/commands/erigon_receipts.go#LL391C24-L391C51
ErigonGetBlockReceiptsByBlockHash
ErigonGetBlockReceiptsByBlockHash
// RethGetBlockReceiptsMDBX is a Reth-specific receipt fetching method. It reads the data directly from reth's database, using their
// generic DB abstractions, rather than requesting it from the RPC provider.
// Available in:
// - Reth
// Method: n/a - does not use RPC.
// Params:
// - Reth: string, hex-encoded block hash
// Returns:
// - Reth: array of RLP-encoded receipts
// See:
// - reth's DB crate documentation: https://github.com/paradigmxyz/reth/blob/main/docs/crates/db.md
RethGetBlockReceiptsMDBX
// Other:
// Other:
// - 250 credits, not supported, strictly worse than other options. In quicknode price-table.
// - 250 credits, not supported, strictly worse than other options. In quicknode price-table.
...
@@ -297,12 +311,14 @@ func AvailableReceiptsFetchingMethods(kind RPCProviderKind) ReceiptsFetchingMeth
...
@@ -297,12 +311,14 @@ 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
// if it's any kind of RPC provider, then try all methods
(except for RethGetBlockReceiptsMDBX)
return
AlchemyGetTransactionReceipts
|
EthGetBlockReceipts
|
return
AlchemyGetTransactionReceipts
|
EthGetBlockReceipts
|
DebugGetRawReceipts
|
ErigonGetBlockReceiptsByBlockHash
|
DebugGetRawReceipts
|
ErigonGetBlockReceiptsByBlockHash
|
ParityGetBlockReceipts
|
EthGetTransactionReceiptBatch
ParityGetBlockReceipts
|
EthGetTransactionReceiptBatch
case
RPCKindStandard
:
case
RPCKindStandard
:
return
EthGetBlockReceipts
|
EthGetTransactionReceiptBatch
return
EthGetBlockReceipts
|
EthGetTransactionReceiptBatch
case
RPCKindRethDB
:
return
RethGetBlockReceiptsMDBX
default
:
default
:
return
EthGetTransactionReceiptBatch
return
EthGetTransactionReceiptBatch
}
}
...
@@ -313,7 +329,9 @@ func AvailableReceiptsFetchingMethods(kind RPCProviderKind) ReceiptsFetchingMeth
...
@@ -313,7 +329,9 @@ func AvailableReceiptsFetchingMethods(kind RPCProviderKind) ReceiptsFetchingMeth
func
PickBestReceiptsFetchingMethod
(
kind
RPCProviderKind
,
available
ReceiptsFetchingMethod
,
txCount
uint64
)
ReceiptsFetchingMethod
{
func
PickBestReceiptsFetchingMethod
(
kind
RPCProviderKind
,
available
ReceiptsFetchingMethod
,
txCount
uint64
)
ReceiptsFetchingMethod
{
// If we have optimized methods available, it makes sense to use them, but only if the cost is
// If we have optimized methods available, it makes sense to use them, but only if the cost is
// lower than fetching transactions one by one with the standard receipts RPC method.
// lower than fetching transactions one by one with the standard receipts RPC method.
if
kind
==
RPCKindAlchemy
{
if
kind
==
RPCKindRethDB
{
return
RethGetBlockReceiptsMDBX
}
else
if
kind
==
RPCKindAlchemy
{
if
available
&
AlchemyGetTransactionReceipts
!=
0
&&
txCount
>
250
/
15
{
if
available
&
AlchemyGetTransactionReceipts
!=
0
&&
txCount
>
250
/
15
{
return
AlchemyGetTransactionReceipts
return
AlchemyGetTransactionReceipts
}
}
...
@@ -460,6 +478,12 @@ func (job *receiptsFetchingJob) runAltMethod(ctx context.Context, m ReceiptsFetc
...
@@ -460,6 +478,12 @@ func (job *receiptsFetchingJob) runAltMethod(ctx context.Context, m ReceiptsFetc
err
=
job
.
client
.
CallContext
(
ctx
,
&
result
,
"eth_getBlockReceipts"
,
job
.
block
.
Hash
)
err
=
job
.
client
.
CallContext
(
ctx
,
&
result
,
"eth_getBlockReceipts"
,
job
.
block
.
Hash
)
case
ErigonGetBlockReceiptsByBlockHash
:
case
ErigonGetBlockReceiptsByBlockHash
:
err
=
job
.
client
.
CallContext
(
ctx
,
&
result
,
"erigon_getBlockReceiptsByBlockHash"
,
job
.
block
.
Hash
)
err
=
job
.
client
.
CallContext
(
ctx
,
&
result
,
"erigon_getBlockReceiptsByBlockHash"
,
job
.
block
.
Hash
)
case
RethGetBlockReceiptsMDBX
:
res
,
err
:=
FetchRethReceipts
(
"placeholder"
,
&
job
.
block
.
Hash
)
if
err
!=
nil
{
return
err
}
result
=
res
default
:
default
:
err
=
fmt
.
Errorf
(
"unknown receipt fetching method: %d"
,
uint64
(
m
))
err
=
fmt
.
Errorf
(
"unknown receipt fetching method: %d"
,
uint64
(
m
))
}
}
...
...
op-service/sources/reth_db.go
0 → 100644
View file @
80d619ab
package
sources
import
(
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
/*
#cgo LDFLAGS: -L../rethdb-reader/target/release -lrethdbreader
#include <stdlib.h>
#include <stdint.h>
typedef struct {
uint8_t* data;
size_t len;
} ByteArray;
typedef struct {
ByteArray* data;
size_t len;
} ByteArrays;
extern ByteArrays read_receipts(const uint8_t* block_hash, size_t block_hash_len, const char* db_path);
extern void free_byte_arrays(ByteArrays arrays);
*/
import
"C"
import
"unsafe"
// 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.
func
FetchRethReceipts
(
dbPath
string
,
blockHash
*
common
.
Hash
)
(
types
.
Receipts
,
error
)
{
if
blockHash
==
nil
{
return
nil
,
fmt
.
Errorf
(
"Must provide a block hash to fetch receipts for."
)
}
// Convert the block hash to a C byte array and defer its deallocation
cBlockHash
:=
C
.
CBytes
(
blockHash
[
:
])
defer
C
.
free
(
cBlockHash
)
// Convert the db path to a C string and defer its deallocation
cDbPath
:=
C
.
CString
(
dbPath
)
defer
C
.
free
(
unsafe
.
Pointer
(
cDbPath
))
// 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
)
// Convert the returned receipt RLP byte arrays to decoded Receipts.
data
:=
make
(
types
.
Receipts
,
byteArrayStruct
.
len
)
byteArraySlice
:=
(
*
[
1
<<
30
]
C
.
ByteArray
)(
unsafe
.
Pointer
(
byteArrayStruct
.
data
))[
:
byteArrayStruct
.
len
:
byteArrayStruct
.
len
]
for
i
,
byteArray
:=
range
byteArraySlice
{
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
C
.
free_byte_arrays
(
byteArrayStruct
)
return
data
,
nil
}
op-service/sources/reth_db_test.go
0 → 100644
View file @
80d619ab
package
sources
import
(
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func
TestRethReceiptsLoad
(
t
*
testing
.
T
)
{
t
.
Skip
(
"Skipping test that requires a local L1 Goerli Reth DB"
)
t
.
Parallel
()
// block = https://goerli.etherscan.io/block/994113
blockHash
:=
common
.
HexToHash
(
"0x6f6f00553e4f74262a9812927afd11c341730c5c9210824fe172367457adb5f6"
)
res
,
err
:=
FetchRethReceipts
(
"/path/to/goerli-db"
,
&
blockHash
)
require
.
NoError
(
t
,
err
,
"Failed to fetch receipts from Reth DB"
)
require
.
Len
(
t
,
res
,
2
,
"Expected 2 receipts to be returned"
)
require
.
Equal
(
t
,
res
[
0
]
.
Type
,
0
)
require
.
Equal
(
t
,
res
[
0
]
.
CumulativeGasUsed
,
uint64
(
93
_787
))
require
.
Equal
(
t
,
res
[
0
]
.
Status
,
uint64
(
1
))
}
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