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:
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