mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(examples): OP Stack bridge stats ExEx (#7556)
Co-authored-by: Oliver Nordbjerg <hi@notbjerg.me> Co-authored-by: Oliver Nordbjerg <onbjerg@users.noreply.github.com>
This commit is contained in:
78
Cargo.lock
generated
78
Cargo.lock
generated
@ -452,6 +452,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "452d929748ac948a10481fff4123affead32c553cf362841c5103dd508bdfc16"
|
||||
dependencies = [
|
||||
"alloy-json-abi",
|
||||
"alloy-sol-macro-input",
|
||||
"const-hex",
|
||||
"heck 0.4.1",
|
||||
@ -470,11 +471,13 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df64e094f6d2099339f9e82b5b38440b159757b6920878f28316243f8166c8d1"
|
||||
dependencies = [
|
||||
"alloy-json-abi",
|
||||
"const-hex",
|
||||
"dunce",
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_json",
|
||||
"syn 2.0.58",
|
||||
"syn-solidity",
|
||||
]
|
||||
@ -494,6 +497,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43bc2d6dfc2a19fd56644494479510f98b1ee929e04cf0d4aa45e98baa3e545b"
|
||||
dependencies = [
|
||||
"alloy-json-abi",
|
||||
"alloy-primitives",
|
||||
"alloy-sol-macro",
|
||||
"const-hex",
|
||||
@ -2396,7 +2400,7 @@ dependencies = [
|
||||
"enr",
|
||||
"fnv",
|
||||
"futures",
|
||||
"hashlink",
|
||||
"hashlink 0.8.4",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"lazy_static",
|
||||
@ -2751,6 +2755,18 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fast-float"
|
||||
version = "0.2.0"
|
||||
@ -3191,6 +3207,15 @@ dependencies = [
|
||||
"hashbrown 0.14.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692eaaf7f7607518dd3cef090f1474b61edc5301d8012f09579920df68b725ee"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hdrhistogram"
|
||||
version = "7.5.4"
|
||||
@ -4408,6 +4433,17 @@ dependencies = [
|
||||
"libsecp256k1-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
@ -5029,6 +5065,26 @@ version = "11.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
|
||||
[[package]]
|
||||
name = "op-bridge"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"alloy-sol-types",
|
||||
"eyre",
|
||||
"futures",
|
||||
"itertools 0.12.1",
|
||||
"reth",
|
||||
"reth-exex",
|
||||
"reth-node-api",
|
||||
"reth-node-core",
|
||||
"reth-node-ethereum",
|
||||
"reth-primitives",
|
||||
"reth-provider",
|
||||
"reth-tracing",
|
||||
"rusqlite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
@ -7654,6 +7710,20 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343"
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink 0.9.0",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
@ -9444,6 +9514,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "vergen"
|
||||
version = "8.3.1"
|
||||
|
||||
@ -77,6 +77,7 @@ members = [
|
||||
"examples/trace-transaction-cli/",
|
||||
"examples/polygon-p2p/",
|
||||
"examples/custom-inspector/",
|
||||
"examples/exex/op-bridge/",
|
||||
"testing/ef-tests/",
|
||||
]
|
||||
default-members = ["bin/reth"]
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use reth_primitives::BlockNumber;
|
||||
|
||||
/// Events emitted by an ExEx.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExExEvent {
|
||||
/// Highest block processed by the ExEx.
|
||||
///
|
||||
|
||||
@ -8,7 +8,7 @@ use reth_primitives::{
|
||||
};
|
||||
use reth_trie::updates::TrieUpdates;
|
||||
use revm::db::BundleState;
|
||||
use std::{borrow::Cow, collections::BTreeMap, fmt};
|
||||
use std::{borrow::Cow, collections::BTreeMap, fmt, ops::RangeInclusive};
|
||||
|
||||
/// A chain of blocks and their final state.
|
||||
///
|
||||
@ -177,6 +177,11 @@ impl Chain {
|
||||
self.blocks.len()
|
||||
}
|
||||
|
||||
/// Returns the range of block numbers in the chain.
|
||||
pub fn range(&self) -> RangeInclusive<BlockNumber> {
|
||||
self.first().number..=self.tip().number
|
||||
}
|
||||
|
||||
/// Get all receipts for the given block.
|
||||
pub fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option<Vec<&Receipt>> {
|
||||
let num = self.block_number(block_hash)?;
|
||||
|
||||
@ -7,14 +7,11 @@ license.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
reth-primitives.workspace = true
|
||||
|
||||
reth-db.workspace = true
|
||||
reth-provider.workspace = true
|
||||
|
||||
reth-rpc-builder.workspace = true
|
||||
reth-rpc-types.workspace = true
|
||||
reth-rpc-types-compat.workspace = true
|
||||
|
||||
reth-revm.workspace = true
|
||||
reth-blockchain-tree.workspace = true
|
||||
reth-beacon-consensus.workspace = true
|
||||
@ -22,6 +19,7 @@ reth-network-api.workspace = true
|
||||
reth-network.workspace = true
|
||||
reth-transaction-pool.workspace = true
|
||||
reth-tasks.workspace = true
|
||||
|
||||
eyre.workspace = true
|
||||
futures.workspace = true
|
||||
async-trait.workspace = true
|
||||
@ -38,3 +36,4 @@ path = "network.rs"
|
||||
[[example]]
|
||||
name = "network-txpool"
|
||||
path = "network-txpool.rs"
|
||||
|
||||
|
||||
@ -7,4 +7,4 @@ license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
|
||||
23
examples/exex/op-bridge/Cargo.toml
Normal file
23
examples/exex/op-bridge/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "op-bridge"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
reth.workspace = true
|
||||
reth-exex.workspace = true
|
||||
reth-node-api.workspace = true
|
||||
reth-node-core.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-provider.workspace = true
|
||||
reth-tracing.workspace = true
|
||||
|
||||
eyre.workspace = true
|
||||
tokio.workspace = true
|
||||
futures.workspace = true
|
||||
alloy-sol-types = { workspace = true, features = ["json"] }
|
||||
itertools.workspace = true
|
||||
rusqlite = { version = "0.31.0", features = ["bundled"] }
|
||||
664
examples/exex/op-bridge/l1_standard_bridge_abi.json
Normal file
664
examples/exex/op-bridge/l1_standard_bridge_abi.json
Normal file
@ -0,0 +1,664 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address payable",
|
||||
"name": "_messenger",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "localToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "remoteToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes",
|
||||
"name": "extraData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ERC20BridgeFinalized",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "localToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "remoteToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes",
|
||||
"name": "extraData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ERC20BridgeInitiated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "l1Token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "l2Token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes",
|
||||
"name": "extraData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ERC20DepositInitiated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "l1Token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "l2Token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes",
|
||||
"name": "extraData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ERC20WithdrawalFinalized",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes",
|
||||
"name": "extraData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ETHBridgeFinalized",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes",
|
||||
"name": "extraData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ETHBridgeInitiated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes",
|
||||
"name": "extraData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ETHDepositInitiated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes",
|
||||
"name": "extraData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ETHWithdrawalFinalized",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint8",
|
||||
"name": "version",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"name": "Initialized",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "MESSENGER",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract CrossDomainMessenger",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "OTHER_BRIDGE",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract StandardBridge",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_localToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_remoteToken",
|
||||
"type": "address"
|
||||
},
|
||||
{ "internalType": "uint256", "name": "_amount", "type": "uint256" },
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "_minGasLimit",
|
||||
"type": "uint32"
|
||||
},
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "bridgeERC20",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_localToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_remoteToken",
|
||||
"type": "address"
|
||||
},
|
||||
{ "internalType": "address", "name": "_to", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "_amount", "type": "uint256" },
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "_minGasLimit",
|
||||
"type": "uint32"
|
||||
},
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "bridgeERC20To",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "_minGasLimit",
|
||||
"type": "uint32"
|
||||
},
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "bridgeETH",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "_to", "type": "address" },
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "_minGasLimit",
|
||||
"type": "uint32"
|
||||
},
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "bridgeETHTo",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_l1Token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_l2Token",
|
||||
"type": "address"
|
||||
},
|
||||
{ "internalType": "uint256", "name": "_amount", "type": "uint256" },
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "_minGasLimit",
|
||||
"type": "uint32"
|
||||
},
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "depositERC20",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_l1Token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_l2Token",
|
||||
"type": "address"
|
||||
},
|
||||
{ "internalType": "address", "name": "_to", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "_amount", "type": "uint256" },
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "_minGasLimit",
|
||||
"type": "uint32"
|
||||
},
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "depositERC20To",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "_minGasLimit",
|
||||
"type": "uint32"
|
||||
},
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "depositETH",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "_to", "type": "address" },
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "_minGasLimit",
|
||||
"type": "uint32"
|
||||
},
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "depositETHTo",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "", "type": "address" },
|
||||
{ "internalType": "address", "name": "", "type": "address" }
|
||||
],
|
||||
"name": "deposits",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "", "type": "uint256" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_localToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_remoteToken",
|
||||
"type": "address"
|
||||
},
|
||||
{ "internalType": "address", "name": "_from", "type": "address" },
|
||||
{ "internalType": "address", "name": "_to", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "_amount", "type": "uint256" },
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "finalizeBridgeERC20",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "_from", "type": "address" },
|
||||
{ "internalType": "address", "name": "_to", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "_amount", "type": "uint256" },
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "finalizeBridgeETH",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_l1Token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_l2Token",
|
||||
"type": "address"
|
||||
},
|
||||
{ "internalType": "address", "name": "_from", "type": "address" },
|
||||
{ "internalType": "address", "name": "_to", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "_amount", "type": "uint256" },
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "finalizeERC20Withdrawal",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "_from", "type": "address" },
|
||||
{ "internalType": "address", "name": "_to", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "_amount", "type": "uint256" },
|
||||
{ "internalType": "bytes", "name": "_extraData", "type": "bytes" }
|
||||
],
|
||||
"name": "finalizeETHWithdrawal",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "contract SuperchainConfig",
|
||||
"name": "_superchainConfig",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "initialize",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "l2TokenBridge",
|
||||
"outputs": [
|
||||
{ "internalType": "address", "name": "", "type": "address" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "messenger",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract CrossDomainMessenger",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "otherBridge",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract StandardBridge",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "paused",
|
||||
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "superchainConfig",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract SuperchainConfig",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "version",
|
||||
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{ "stateMutability": "payable", "type": "receive" }
|
||||
]
|
||||
244
examples/exex/op-bridge/src/main.rs
Normal file
244
examples/exex/op-bridge/src/main.rs
Normal file
@ -0,0 +1,244 @@
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{ready, Context, Poll},
|
||||
};
|
||||
|
||||
use alloy_sol_types::{sol, SolEventInterface};
|
||||
use futures::Future;
|
||||
use reth::builder::FullNodeTypes;
|
||||
use reth_exex::{ExExContext, ExExEvent};
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
use reth_primitives::{Log, SealedBlockWithSenders, TransactionSigned};
|
||||
use reth_provider::Chain;
|
||||
use reth_tracing::tracing::info;
|
||||
use rusqlite::Connection;
|
||||
|
||||
sol!(L1StandardBridge, "l1_standard_bridge_abi.json");
|
||||
use crate::L1StandardBridge::{ETHBridgeFinalized, ETHBridgeInitiated, L1StandardBridgeEvents};
|
||||
|
||||
/// An example of ExEx that listens to ETH bridging events from OP Stack chains
|
||||
/// and stores deposits and withdrawals in a SQLite database.
|
||||
struct OPBridgeExEx<Node: FullNodeTypes> {
|
||||
ctx: ExExContext<Node>,
|
||||
connection: Connection,
|
||||
}
|
||||
|
||||
impl<Node: FullNodeTypes> OPBridgeExEx<Node> {
|
||||
fn new(ctx: ExExContext<Node>, connection: Connection) -> eyre::Result<Self> {
|
||||
// Create deposits and withdrawals tables
|
||||
connection.execute(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS deposits (
|
||||
id INTEGER PRIMARY KEY,
|
||||
block_number INTEGER NOT NULL,
|
||||
tx_hash TEXT NOT NULL UNIQUE,
|
||||
contract_address TEXT NOT NULL,
|
||||
"from" TEXT NOT NULL,
|
||||
"to" TEXT NOT NULL,
|
||||
amount TEXT NOT NULL
|
||||
);
|
||||
"#,
|
||||
(),
|
||||
)?;
|
||||
connection.execute(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS withdrawals (
|
||||
id INTEGER PRIMARY KEY,
|
||||
block_number INTEGER NOT NULL,
|
||||
tx_hash TEXT NOT NULL UNIQUE,
|
||||
contract_address TEXT NOT NULL,
|
||||
"from" TEXT NOT NULL,
|
||||
"to" TEXT NOT NULL,
|
||||
amount TEXT NOT NULL
|
||||
);
|
||||
"#,
|
||||
(),
|
||||
)?;
|
||||
|
||||
// Create a bridge contract addresses table and insert known ones with their respective
|
||||
// names
|
||||
connection.execute(
|
||||
r#"
|
||||
CREATE TABLE IF NOT EXISTS contracts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
address TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
"#,
|
||||
(),
|
||||
)?;
|
||||
connection.execute(
|
||||
r#"
|
||||
INSERT OR IGNORE INTO contracts (address, name)
|
||||
VALUES
|
||||
('0x3154Cf16ccdb4C6d922629664174b904d80F2C35', 'Base'),
|
||||
('0x3a05E5d33d7Ab3864D53aaEc93c8301C1Fa49115', 'Blast'),
|
||||
('0x697402166Fbf2F22E970df8a6486Ef171dbfc524', 'Blast'),
|
||||
('0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1', 'Optimism'),
|
||||
('0x735aDBbE72226BD52e818E7181953f42E3b0FF21', 'Mode'),
|
||||
('0x3B95bC951EE0f553ba487327278cAc44f29715E5', 'Manta');
|
||||
"#,
|
||||
(),
|
||||
)?;
|
||||
|
||||
info!("Initialized database tables");
|
||||
|
||||
Ok(Self { ctx, connection })
|
||||
}
|
||||
}
|
||||
|
||||
impl<Node: FullNodeTypes> Future for OPBridgeExEx<Node> {
|
||||
type Output = eyre::Result<()>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.get_mut();
|
||||
|
||||
// Process all new chain state notifications until there are no more
|
||||
while let Some(notification) = ready!(this.ctx.notifications.poll_recv(cx)) {
|
||||
// If there was a reorg, delete all deposits and withdrawals that were reverted
|
||||
if let Some(reverted_chain) = notification.reverted() {
|
||||
let events = decode_chain_into_events(&reverted_chain);
|
||||
|
||||
let mut deposits = 0;
|
||||
let mut withdrawals = 0;
|
||||
|
||||
for (_, tx, _, event) in events {
|
||||
match event {
|
||||
// L1 -> L2 deposit
|
||||
L1StandardBridgeEvents::ETHBridgeInitiated(ETHBridgeInitiated {
|
||||
..
|
||||
}) => {
|
||||
let deleted = this.connection.execute(
|
||||
"DELETE FROM deposits WHERE tx_hash = ?;",
|
||||
(tx.hash().to_string(),),
|
||||
)?;
|
||||
deposits += deleted;
|
||||
}
|
||||
// L2 -> L1 withdrawal
|
||||
L1StandardBridgeEvents::ETHBridgeFinalized(ETHBridgeFinalized {
|
||||
..
|
||||
}) => {
|
||||
let deleted = this.connection.execute(
|
||||
"DELETE FROM withdrawals WHERE tx_hash = ?;",
|
||||
(tx.hash().to_string(),),
|
||||
)?;
|
||||
withdrawals += deleted;
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
|
||||
info!(block_range = ?reverted_chain.range(), %deposits, %withdrawals, "Reverted chain events");
|
||||
}
|
||||
|
||||
// Insert all new deposits and withdrawals
|
||||
let committed_chain = notification.committed();
|
||||
let events = decode_chain_into_events(&committed_chain);
|
||||
|
||||
let mut deposits = 0;
|
||||
let mut withdrawals = 0;
|
||||
|
||||
for (block, tx, log, event) in events {
|
||||
match event {
|
||||
// L1 -> L2 deposit
|
||||
L1StandardBridgeEvents::ETHBridgeInitiated(ETHBridgeInitiated {
|
||||
amount,
|
||||
from,
|
||||
to,
|
||||
..
|
||||
}) => {
|
||||
let inserted = this.connection.execute(
|
||||
r#"
|
||||
INSERT INTO deposits (block_number, tx_hash, contract_address, "from", "to", amount)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
(
|
||||
block.number,
|
||||
tx.hash().to_string(),
|
||||
log.address.to_string(),
|
||||
from.to_string(),
|
||||
to.to_string(),
|
||||
amount.to_string(),
|
||||
),
|
||||
)?;
|
||||
deposits += inserted;
|
||||
}
|
||||
// L2 -> L1 withdrawal
|
||||
L1StandardBridgeEvents::ETHBridgeFinalized(ETHBridgeFinalized {
|
||||
amount,
|
||||
from,
|
||||
to,
|
||||
..
|
||||
}) => {
|
||||
let inserted = this.connection.execute(
|
||||
r#"
|
||||
INSERT INTO withdrawals (block_number, tx_hash, contract_address, "from", "to", amount)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
"#,
|
||||
(
|
||||
block.number,
|
||||
tx.hash().to_string(),
|
||||
log.address.to_string(),
|
||||
from.to_string(),
|
||||
to.to_string(),
|
||||
amount.to_string(),
|
||||
),
|
||||
)?;
|
||||
withdrawals += inserted;
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
|
||||
info!(block_range = ?committed_chain.range(), %deposits, %withdrawals, "Committed chain events");
|
||||
|
||||
// Send a finished height event, signaling the node that we don't need any blocks below
|
||||
// this height anymore
|
||||
this.ctx.events.send(ExExEvent::FinishedHeight(notification.tip().number))?;
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode chain of blocks into a flattened list of receipt logs, and filter only
|
||||
/// [L1StandardBridgeEvents].
|
||||
fn decode_chain_into_events(
|
||||
chain: &Chain,
|
||||
) -> impl Iterator<Item = (&SealedBlockWithSenders, &TransactionSigned, &Log, L1StandardBridgeEvents)>
|
||||
{
|
||||
chain
|
||||
// Get all blocks and receipts
|
||||
.blocks_and_receipts()
|
||||
// Get all receipts
|
||||
.flat_map(|(block, receipts)| {
|
||||
block
|
||||
.body
|
||||
.iter()
|
||||
.zip(receipts.iter().flatten())
|
||||
.map(move |(tx, receipt)| (block, tx, receipt))
|
||||
})
|
||||
// Get all logs
|
||||
.flat_map(|(block, tx, receipt)| receipt.logs.iter().map(move |log| (block, tx, log)))
|
||||
// Decode and filter bridge events
|
||||
.filter_map(|(block, tx, log)| {
|
||||
L1StandardBridgeEvents::decode_raw_log(&log.topics, &log.data, true)
|
||||
.ok()
|
||||
.map(|event| (block, tx, log, event))
|
||||
})
|
||||
}
|
||||
|
||||
fn main() -> eyre::Result<()> {
|
||||
reth::cli::Cli::parse_args().run(|builder, _| async move {
|
||||
let handle = builder
|
||||
.node(EthereumNode::default())
|
||||
.install_exex("OPBridge", move |ctx| async {
|
||||
let connection = Connection::open("op_bridge.db")?;
|
||||
OPBridgeExEx::new(ctx, connection)
|
||||
})
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
handle.wait_for_node_exit().await
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user