mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(examples): sqlite rollup exex (#7826)
This commit is contained in:
38
examples/exex/rollup/Cargo.toml
Normal file
38
examples/exex/rollup/Cargo.toml
Normal file
@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "exex-rollup"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
# reth
|
||||
reth.workspace = true
|
||||
reth-cli-runner.workspace = true
|
||||
reth-exex.workspace = true
|
||||
reth-interfaces.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-revm.workspace = true
|
||||
reth-tracing.workspace = true
|
||||
reth-trie.workspace = true
|
||||
|
||||
# async
|
||||
tokio.workspace = true
|
||||
futures.workspace = true
|
||||
|
||||
# misc
|
||||
alloy-sol-types = { workspace = true, features = ["json"] }
|
||||
alloy-rlp.workspace = true
|
||||
eyre.workspace = true
|
||||
rusqlite = { version = "0.31.0", features = ["bundled"] }
|
||||
serde_json.workspace = true
|
||||
once_cell.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
reth-interfaces = { workspace = true, features = ["test-utils"] }
|
||||
secp256k1.workspace = true
|
||||
|
||||
626
examples/exex/rollup/rollup_abi.json
Normal file
626
examples/exex/rollup/rollup_abi.json
Normal file
@ -0,0 +1,626 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "admin", "type": "address" }
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{ "inputs": [], "name": "AccessControlBadConfirmation", "type": "error" },
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint48", "name": "schedule", "type": "uint48" }
|
||||
],
|
||||
"name": "AccessControlEnforcedDefaultAdminDelay",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "AccessControlEnforcedDefaultAdminRules",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "defaultAdmin",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "AccessControlInvalidDefaultAdmin",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "account", "type": "address" },
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "neededRole",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "AccessControlUnauthorizedAccount",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "expected", "type": "uint256" }
|
||||
],
|
||||
"name": "BadSequence",
|
||||
"type": "error"
|
||||
},
|
||||
{ "inputs": [], "name": "BadSignature", "type": "error" },
|
||||
{ "inputs": [], "name": "BlockExpired", "type": "error" },
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "sequencer",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "NotSequencer",
|
||||
"type": "error"
|
||||
},
|
||||
{ "inputs": [], "name": "OrderExpired", "type": "error" },
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint8", "name": "bits", "type": "uint8" },
|
||||
{ "internalType": "uint256", "name": "value", "type": "uint256" }
|
||||
],
|
||||
"name": "SafeCastOverflowedUintDowncast",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "sequencer",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "rollupChainId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "sequence",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "confirmBy",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "gasLimit",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "rewardAddress",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"indexed": true,
|
||||
"internalType": "struct CalldataZenith.BlockHeader",
|
||||
"name": "header",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes",
|
||||
"name": "blockData",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "BlockSubmitted",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [],
|
||||
"name": "DefaultAdminDelayChangeCanceled",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint48",
|
||||
"name": "newDelay",
|
||||
"type": "uint48"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint48",
|
||||
"name": "effectSchedule",
|
||||
"type": "uint48"
|
||||
}
|
||||
],
|
||||
"name": "DefaultAdminDelayChangeScheduled",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [],
|
||||
"name": "DefaultAdminTransferCanceled",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newAdmin",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint48",
|
||||
"name": "acceptSchedule",
|
||||
"type": "uint48"
|
||||
}
|
||||
],
|
||||
"name": "DefaultAdminTransferScheduled",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "rollupRecipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Enter",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "hostRecipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "ExitFilled",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "bytes32",
|
||||
"name": "role",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "bytes32",
|
||||
"name": "previousAdminRole",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "bytes32",
|
||||
"name": "newAdminRole",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "RoleAdminChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "bytes32",
|
||||
"name": "role",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "sender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "RoleGranted",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "bytes32",
|
||||
"name": "role",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "account",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "sender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "RoleRevoked",
|
||||
"type": "event"
|
||||
},
|
||||
{ "stateMutability": "payable", "type": "fallback" },
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "DEFAULT_ADMIN_ROLE",
|
||||
"outputs": [
|
||||
{ "internalType": "bytes32", "name": "", "type": "bytes32" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "SEQUENCER_ROLE",
|
||||
"outputs": [
|
||||
{ "internalType": "bytes32", "name": "", "type": "bytes32" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "acceptDefaultAdminTransfer",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "newAdmin", "type": "address" }
|
||||
],
|
||||
"name": "beginDefaultAdminTransfer",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "rollupChainId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "sequence",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "confirmBy",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "gasLimit",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "rewardAddress",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"internalType": "struct CalldataZenith.BlockHeader",
|
||||
"name": "header",
|
||||
"type": "tuple"
|
||||
},
|
||||
{ "internalType": "bytes", "name": "blockData", "type": "bytes" }
|
||||
],
|
||||
"name": "blockCommitment",
|
||||
"outputs": [
|
||||
{ "internalType": "bytes32", "name": "commit", "type": "bytes32" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "cancelDefaultAdminTransfer",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint48", "name": "newDelay", "type": "uint48" }
|
||||
],
|
||||
"name": "changeDefaultAdminDelay",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "defaultAdmin",
|
||||
"outputs": [
|
||||
{ "internalType": "address", "name": "", "type": "address" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "defaultAdminDelay",
|
||||
"outputs": [{ "internalType": "uint48", "name": "", "type": "uint48" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "defaultAdminDelayIncreaseWait",
|
||||
"outputs": [{ "internalType": "uint48", "name": "", "type": "uint48" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "rollupRecipient",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "enter",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "recipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "deadline",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct HostPassage.ExitOrder[]",
|
||||
"name": "orders",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"name": "fulfillExits",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "bytes32", "name": "role", "type": "bytes32" }
|
||||
],
|
||||
"name": "getRoleAdmin",
|
||||
"outputs": [
|
||||
{ "internalType": "bytes32", "name": "", "type": "bytes32" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "bytes32", "name": "role", "type": "bytes32" },
|
||||
{ "internalType": "address", "name": "account", "type": "address" }
|
||||
],
|
||||
"name": "grantRole",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "bytes32", "name": "role", "type": "bytes32" },
|
||||
{ "internalType": "address", "name": "account", "type": "address" }
|
||||
],
|
||||
"name": "hasRole",
|
||||
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "", "type": "uint256" }
|
||||
],
|
||||
"name": "nextSequence",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "", "type": "uint256" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{ "internalType": "address", "name": "", "type": "address" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "pendingDefaultAdmin",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newAdmin",
|
||||
"type": "address"
|
||||
},
|
||||
{ "internalType": "uint48", "name": "schedule", "type": "uint48" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "pendingDefaultAdminDelay",
|
||||
"outputs": [
|
||||
{ "internalType": "uint48", "name": "newDelay", "type": "uint48" },
|
||||
{ "internalType": "uint48", "name": "schedule", "type": "uint48" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "bytes32", "name": "role", "type": "bytes32" },
|
||||
{ "internalType": "address", "name": "account", "type": "address" }
|
||||
],
|
||||
"name": "renounceRole",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "bytes32", "name": "role", "type": "bytes32" },
|
||||
{ "internalType": "address", "name": "account", "type": "address" }
|
||||
],
|
||||
"name": "revokeRole",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "rollbackDefaultAdminDelay",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "rollupChainId",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "sequence",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "confirmBy",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "gasLimit",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "rewardAddress",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"internalType": "struct CalldataZenith.BlockHeader",
|
||||
"name": "header",
|
||||
"type": "tuple"
|
||||
},
|
||||
{ "internalType": "bytes", "name": "blockData", "type": "bytes" },
|
||||
{ "internalType": "uint8", "name": "v", "type": "uint8" },
|
||||
{ "internalType": "bytes32", "name": "r", "type": "bytes32" },
|
||||
{ "internalType": "bytes32", "name": "s", "type": "bytes32" }
|
||||
],
|
||||
"name": "submitBlock",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes4",
|
||||
"name": "interfaceId",
|
||||
"type": "bytes4"
|
||||
}
|
||||
],
|
||||
"name": "supportsInterface",
|
||||
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{ "stateMutability": "payable", "type": "receive" }
|
||||
]
|
||||
460
examples/exex/rollup/src/db.rs
Normal file
460
examples/exex/rollup/src/db.rs
Normal file
@ -0,0 +1,460 @@
|
||||
use std::{
|
||||
collections::{hash_map::Entry, HashMap},
|
||||
str::FromStr,
|
||||
sync::{Arc, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use reth_primitives::{
|
||||
revm_primitives::{AccountInfo, Bytecode},
|
||||
Address, Bytes, SealedBlockWithSenders, StorageEntry, B256, U256,
|
||||
};
|
||||
use reth_provider::{bundle_state::StorageRevertsIter, OriginalValuesKnown};
|
||||
use reth_revm::db::{
|
||||
states::{PlainStorageChangeset, PlainStorageRevert},
|
||||
BundleState,
|
||||
};
|
||||
use rusqlite::Connection;
|
||||
|
||||
/// Type used to initialize revms bundle state.
|
||||
type BundleStateInit =
|
||||
HashMap<Address, (Option<AccountInfo>, Option<AccountInfo>, HashMap<B256, (U256, U256)>)>;
|
||||
|
||||
/// Types used inside RevertsInit to initialize revms reverts.
|
||||
pub type AccountRevertInit = (Option<Option<AccountInfo>>, Vec<StorageEntry>);
|
||||
|
||||
/// Type used to initialize revms reverts.
|
||||
pub type RevertsInit = HashMap<Address, AccountRevertInit>;
|
||||
|
||||
pub struct Database {
|
||||
connection: Arc<Mutex<Connection>>,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
/// Create new database with the provided connection.
|
||||
pub fn new(connection: Connection) -> eyre::Result<Self> {
|
||||
let database = Self { connection: Arc::new(Mutex::new(connection)) };
|
||||
database.create_tables()?;
|
||||
Ok(database)
|
||||
}
|
||||
|
||||
fn connection(&self) -> MutexGuard<'_, Connection> {
|
||||
self.connection.lock().expect("failed to acquire database lock")
|
||||
}
|
||||
|
||||
fn create_tables(&self) -> eyre::Result<()> {
|
||||
self.connection().execute_batch(
|
||||
"CREATE TABLE IF NOT EXISTS block (
|
||||
id INTEGER PRIMARY KEY,
|
||||
number TEXT UNIQUE,
|
||||
data TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS account (
|
||||
id INTEGER PRIMARY KEY,
|
||||
address TEXT UNIQUE,
|
||||
data TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS account_revert (
|
||||
id INTEGER PRIMARY KEY,
|
||||
block_number TEXT,
|
||||
address TEXT,
|
||||
data TEXT,
|
||||
UNIQUE (block_number, address)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS storage (
|
||||
id INTEGER PRIMARY KEY,
|
||||
address TEXT,
|
||||
key TEXT,
|
||||
data TEXT,
|
||||
UNIQUE (address, key)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS storage_revert (
|
||||
id INTEGER PRIMARY KEY,
|
||||
block_number TEXT,
|
||||
address TEXT,
|
||||
key TEXT,
|
||||
data TEXT,
|
||||
UNIQUE (block_number, address, key)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS bytecode (
|
||||
id INTEGER PRIMARY KEY,
|
||||
hash TEXT UNIQUE,
|
||||
data TEXT
|
||||
);",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Insert block with bundle into the database.
|
||||
pub fn insert_block_with_bundle(
|
||||
&self,
|
||||
block: &SealedBlockWithSenders,
|
||||
bundle: BundleState,
|
||||
) -> eyre::Result<()> {
|
||||
let mut connection = self.connection();
|
||||
let tx = connection.transaction()?;
|
||||
|
||||
tx.execute(
|
||||
"INSERT INTO block (number, data) VALUES (?, ?)",
|
||||
(block.header.number.to_string(), serde_json::to_string(block)?),
|
||||
)?;
|
||||
|
||||
let (changeset, reverts) = bundle.into_plain_state_and_reverts(OriginalValuesKnown::Yes);
|
||||
|
||||
for (address, account) in changeset.accounts {
|
||||
if let Some(account) = account {
|
||||
tx.execute(
|
||||
"INSERT INTO account (address, data) VALUES (?, ?) ON CONFLICT(address) DO UPDATE SET data = excluded.data",
|
||||
(address.to_string(), serde_json::to_string(&account)?),
|
||||
)?;
|
||||
} else {
|
||||
tx.execute("DELETE FROM account WHERE address = ?", (address.to_string(),))?;
|
||||
}
|
||||
}
|
||||
|
||||
if reverts.accounts.len() > 1 {
|
||||
eyre::bail!("too many blocks in account reverts");
|
||||
}
|
||||
for (address, account) in
|
||||
reverts.accounts.first().ok_or(eyre::eyre!("no account reverts"))?
|
||||
{
|
||||
tx.execute(
|
||||
"INSERT INTO account_revert (block_number, address, data) VALUES (?, ?, ?) ON CONFLICT(block_number, address) DO UPDATE SET data = excluded.data",
|
||||
(block.header.number.to_string(), address.to_string(), serde_json::to_string(account)?),
|
||||
)?;
|
||||
}
|
||||
|
||||
for PlainStorageChangeset { address, wipe_storage, storage } in changeset.storage {
|
||||
if wipe_storage {
|
||||
tx.execute("DELETE FROM storage WHERE address = ?", (address.to_string(),))?;
|
||||
}
|
||||
|
||||
for (key, data) in storage {
|
||||
tx.execute(
|
||||
"INSERT INTO storage (address, key, data) VALUES (?, ?, ?) ON CONFLICT(address, key) DO UPDATE SET data = excluded.data",
|
||||
(address.to_string(), B256::from(key).to_string(), data.to_string()),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
if reverts.storage.len() > 1 {
|
||||
eyre::bail!("too many blocks in storage reverts");
|
||||
}
|
||||
for PlainStorageRevert { address, wiped, storage_revert } in
|
||||
reverts.storage.into_iter().next().ok_or(eyre::eyre!("no storage reverts"))?
|
||||
{
|
||||
let storage = storage_revert
|
||||
.into_iter()
|
||||
.map(|(k, v)| (B256::new(k.to_be_bytes()), v))
|
||||
.collect::<Vec<_>>();
|
||||
let wiped_storage = if wiped { get_storages(&tx, address)? } else { Vec::new() };
|
||||
for (key, data) in StorageRevertsIter::new(storage, wiped_storage) {
|
||||
tx.execute(
|
||||
"INSERT INTO storage_revert (block_number, address, key, data) VALUES (?, ?, ?, ?) ON CONFLICT(block_number, address, key) DO UPDATE SET data = excluded.data",
|
||||
(block.header.number.to_string(), address.to_string(), key.to_string(), data.to_string()),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
for (hash, bytecode) in changeset.contracts {
|
||||
tx.execute(
|
||||
"INSERT INTO bytecode (hash, data) VALUES (?, ?) ON CONFLICT(hash) DO NOTHING",
|
||||
(hash.to_string(), bytecode.bytes().to_string()),
|
||||
)?;
|
||||
}
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reverts the tip block from the database, checking it against the provided block number.
|
||||
///
|
||||
/// The code is adapted from <https://github.com/paradigmxyz/reth/blob/a8cd1f71a03c773c24659fc28bfed2ba5f2bd97b/crates/storage/provider/src/providers/database/provider.rs#L365-L540>
|
||||
pub fn revert_tip_block(&self, block_number: U256) -> eyre::Result<()> {
|
||||
let mut connection = self.connection();
|
||||
let tx = connection.transaction()?;
|
||||
|
||||
let tip_block_number = tx
|
||||
.query_row::<String, _, _>(
|
||||
"SELECT number FROM block ORDER BY number DESC LIMIT 1",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.map(|data| U256::from_str(&data))??;
|
||||
if block_number != tip_block_number {
|
||||
eyre::bail!("Reverts can only be done from the tip. Attempted to revert block {} with tip block {}", block_number, tip_block_number);
|
||||
}
|
||||
|
||||
tx.execute("DELETE FROM block WHERE number = ?", (block_number.to_string(),))?;
|
||||
|
||||
let mut state = BundleStateInit::new();
|
||||
let mut reverts = RevertsInit::new();
|
||||
|
||||
let account_reverts = tx
|
||||
.prepare("SELECT address, data FROM account_revert WHERE block_number = ?")?
|
||||
.query((block_number.to_string(),))?
|
||||
.mapped(|row| {
|
||||
Ok((
|
||||
Address::from_str(row.get_ref(0)?.as_str()?),
|
||||
serde_json::from_str::<Option<AccountInfo>>(row.get_ref(1)?.as_str()?),
|
||||
))
|
||||
})
|
||||
.map(|result| {
|
||||
let (address, data) = result?;
|
||||
Ok((address?, data?))
|
||||
})
|
||||
.collect::<eyre::Result<Vec<_>>>()?;
|
||||
|
||||
for (address, old_info) in account_reverts {
|
||||
// insert old info into reverts
|
||||
reverts.entry(address).or_default().0 = Some(old_info.clone());
|
||||
|
||||
match state.entry(address) {
|
||||
Entry::Vacant(entry) => {
|
||||
let new_info = get_account(&tx, address)?;
|
||||
entry.insert((old_info, new_info, HashMap::new()));
|
||||
}
|
||||
Entry::Occupied(mut entry) => {
|
||||
// overwrite old account state
|
||||
entry.get_mut().0 = old_info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let storage_reverts = tx
|
||||
.prepare("SELECT address, key, data FROM storage_revert WHERE block_number = ?")?
|
||||
.query((block_number.to_string(),))?
|
||||
.mapped(|row| {
|
||||
Ok((
|
||||
Address::from_str(row.get_ref(0)?.as_str()?),
|
||||
B256::from_str(row.get_ref(1)?.as_str()?),
|
||||
U256::from_str(row.get_ref(2)?.as_str()?),
|
||||
))
|
||||
})
|
||||
.map(|result| {
|
||||
let (address, key, data) = result?;
|
||||
Ok((address?, key?, data?))
|
||||
})
|
||||
.collect::<eyre::Result<Vec<_>>>()?;
|
||||
|
||||
for (address, key, old_data) in storage_reverts.into_iter().rev() {
|
||||
let old_storage = StorageEntry { key, value: old_data };
|
||||
|
||||
// insert old info into reverts
|
||||
reverts.entry(address).or_default().1.push(old_storage);
|
||||
|
||||
// get account state or insert from plain state
|
||||
let account_state = match state.entry(address) {
|
||||
Entry::Vacant(entry) => {
|
||||
let present_info = get_account(&tx, address)?;
|
||||
entry.insert((present_info.clone(), present_info, HashMap::new()))
|
||||
}
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
};
|
||||
|
||||
// match storage
|
||||
match account_state.2.entry(old_storage.key) {
|
||||
Entry::Vacant(entry) => {
|
||||
let new_value = get_storage(&tx, address, old_storage.key)?.unwrap_or_default();
|
||||
entry.insert((old_storage.value, new_value));
|
||||
}
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.get_mut().0 = old_storage.value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// iterate over local plain state remove all account and all storages
|
||||
for (address, (old_account, new_account, storage)) in state {
|
||||
// revert account if needed
|
||||
if old_account != new_account {
|
||||
if let Some(account) = old_account {
|
||||
upsert_account(&tx, address, |_| Ok(account))?;
|
||||
} else {
|
||||
delete_account(&tx, address)?;
|
||||
}
|
||||
}
|
||||
|
||||
// revert storages
|
||||
for (storage_key, (old_storage_value, _new_storage_value)) in storage {
|
||||
// delete previous value
|
||||
delete_storage(&tx, address, storage_key)?;
|
||||
|
||||
// insert value if needed
|
||||
if !old_storage_value.is_zero() {
|
||||
upsert_storage(&tx, address, storage_key, old_storage_value)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get block by number.
|
||||
pub fn get_block(&self, number: U256) -> eyre::Result<Option<SealedBlockWithSenders>> {
|
||||
let block = self.connection().query_row::<String, _, _>(
|
||||
"SELECT data FROM block WHERE number = ?",
|
||||
(number.to_string(),),
|
||||
|row| row.get(0),
|
||||
);
|
||||
match block {
|
||||
Ok(data) => Ok(Some(serde_json::from_str(&data)?)),
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert new account if it does not exist, update otherwise. The provided closure is called
|
||||
/// with the current account, if it exists.
|
||||
pub fn upsert_account(
|
||||
&self,
|
||||
address: Address,
|
||||
f: impl FnOnce(Option<AccountInfo>) -> eyre::Result<AccountInfo>,
|
||||
) -> eyre::Result<()> {
|
||||
upsert_account(&self.connection(), address, f)
|
||||
}
|
||||
|
||||
/// Get account by address.
|
||||
pub fn get_account(&self, address: Address) -> eyre::Result<Option<AccountInfo>> {
|
||||
get_account(&self.connection(), address)
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert new account if it does not exist, update otherwise. The provided closure is called
|
||||
/// with the current account, if it exists. Connection can be either
|
||||
/// [rusqlite::Transaction] or [rusqlite::Connection].
|
||||
fn upsert_account(
|
||||
connection: &Connection,
|
||||
address: Address,
|
||||
f: impl FnOnce(Option<AccountInfo>) -> eyre::Result<AccountInfo>,
|
||||
) -> eyre::Result<()> {
|
||||
let account = get_account(connection, address)?;
|
||||
let account = f(account)?;
|
||||
connection.execute(
|
||||
"INSERT INTO account (address, data) VALUES (?, ?) ON CONFLICT(address) DO UPDATE SET data = excluded.data",
|
||||
(address.to_string(), serde_json::to_string(&account)?),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete account by address. Connection can be either [rusqlite::Transaction] or
|
||||
/// [rusqlite::Connection].
|
||||
fn delete_account(connection: &Connection, address: Address) -> eyre::Result<()> {
|
||||
connection.execute("DELETE FROM account WHERE address = ?", (address.to_string(),))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get account by address using the database connection. Connection can be either
|
||||
/// [rusqlite::Transaction] or [rusqlite::Connection].
|
||||
fn get_account(connection: &Connection, address: Address) -> eyre::Result<Option<AccountInfo>> {
|
||||
match connection.query_row::<String, _, _>(
|
||||
"SELECT data FROM account WHERE address = ?",
|
||||
(address.to_string(),),
|
||||
|row| row.get(0),
|
||||
) {
|
||||
Ok(account_info) => Ok(Some(serde_json::from_str(&account_info)?)),
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert new storage if it does not exist, update otherwise. Connection can be either
|
||||
/// [rusqlite::Transaction] or [rusqlite::Connection].
|
||||
fn upsert_storage(
|
||||
connection: &Connection,
|
||||
address: Address,
|
||||
key: B256,
|
||||
data: U256,
|
||||
) -> eyre::Result<()> {
|
||||
connection.execute(
|
||||
"INSERT INTO storage (address, key, data) VALUES (?, ?, ?) ON CONFLICT(address, key) DO UPDATE SET data = excluded.data",
|
||||
(address.to_string(), key.to_string(), data.to_string()),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete storage by address and key. Connection can be either [rusqlite::Transaction] or
|
||||
/// [rusqlite::Connection].
|
||||
fn delete_storage(connection: &Connection, address: Address, key: B256) -> eyre::Result<()> {
|
||||
connection.execute(
|
||||
"DELETE FROM storage WHERE address = ? AND key = ?",
|
||||
(address.to_string(), key.to_string()),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get all storages for the provided address using the database connection. Connection can be
|
||||
/// either [rusqlite::Transaction] or [rusqlite::Connection].
|
||||
fn get_storages(connection: &Connection, address: Address) -> eyre::Result<Vec<(B256, U256)>> {
|
||||
connection
|
||||
.prepare("SELECT key, data FROM storage WHERE address = ?")?
|
||||
.query((address.to_string(),))?
|
||||
.mapped(|row| {
|
||||
Ok((
|
||||
B256::from_str(row.get_ref(0)?.as_str()?),
|
||||
U256::from_str(row.get_ref(1)?.as_str()?),
|
||||
))
|
||||
})
|
||||
.map(|result| {
|
||||
let (key, data) = result?;
|
||||
Ok((key?, data?))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get storage for the provided address by key using the database connection. Connection can be
|
||||
/// either [rusqlite::Transaction] or [rusqlite::Connection].
|
||||
fn get_storage(connection: &Connection, address: Address, key: B256) -> eyre::Result<Option<U256>> {
|
||||
match connection.query_row::<String, _, _>(
|
||||
"SELECT data FROM storage WHERE address = ? AND key = ?",
|
||||
(address.to_string(), key.to_string()),
|
||||
|row| row.get(0),
|
||||
) {
|
||||
Ok(data) => Ok(Some(U256::from_str(&data)?)),
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
impl reth_revm::Database for Database {
|
||||
type Error = eyre::Report;
|
||||
|
||||
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
|
||||
self.get_account(address)
|
||||
}
|
||||
|
||||
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
|
||||
let bytecode = self.connection().query_row::<String, _, _>(
|
||||
"SELECT data FROM bytecode WHERE hash = ?",
|
||||
(code_hash.to_string(),),
|
||||
|row| row.get(0),
|
||||
);
|
||||
match bytecode {
|
||||
Ok(data) => Ok(Bytecode::new_raw(Bytes::from_str(&data).unwrap())),
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(Bytecode::default()),
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
|
||||
get_storage(&self.connection(), address, index.into()).map(|data| data.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn block_hash(&mut self, number: U256) -> Result<B256, Self::Error> {
|
||||
let block_hash = self.connection().query_row::<String, _, _>(
|
||||
"SELECT hash FROM block WHERE number = ?",
|
||||
(number.to_string(),),
|
||||
|row| row.get(0),
|
||||
);
|
||||
match block_hash {
|
||||
Ok(data) => Ok(B256::from_str(&data).unwrap()),
|
||||
// No special handling for `QueryReturnedNoRows` is needed, because revm does block
|
||||
// number bound checks on its own.
|
||||
// See https://github.com/bluealloy/revm/blob/1ca3d39f6a9e9778f8eb0fcb74fe529345a531b4/crates/interpreter/src/instructions/host.rs#L106-L123.
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
586
examples/exex/rollup/src/main.rs
Normal file
586
examples/exex/rollup/src/main.rs
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user