Skip to content

Commit

Permalink
feat: add local storage cache (#312)
Browse files Browse the repository at this point in the history
* feat: add local storage cache

* fix: use local storage directly

* fix: use database_type in client

* fix: rename db type

* fix: clippy error
  • Loading branch information
qiweiii authored Jan 15, 2024
1 parent 90be4e9 commit 63dc560
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 47 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ impl ClientBuilder {
fallback,
load_external_fallback,
strict_checkpoint_age,
database_type: None,
};

Client::<DB>::new(config)
Expand Down
1 change: 1 addition & 0 deletions config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub struct Config {
pub fallback: Option<String>,
pub load_external_fallback: bool,
pub strict_checkpoint_age: bool,
pub database_type: Option<String>,
}

impl Config {
Expand Down
2 changes: 1 addition & 1 deletion execution/src/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ impl<R: ExecutionRpc> ExecutionClient<R> {
}

async fn verify_logs(&self, logs: &[Log]) -> Result<()> {
for (_pos, log) in logs.iter().enumerate() {
for log in logs.iter() {
// For every log
// Get the hash of the tx that generated it
let tx_hash = log
Expand Down
8 changes: 4 additions & 4 deletions helios-ts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ wasm-bindgen-test = "0.3.0"
serde-wasm-bindgen = "0.4.5"
console_error_panic_hook = "0.1.7"

eyre.workspace = true

ethers = "2.0.2"
hex = "0.4.3"
serde = { version = "1.0.143", features = ["derive"] }
Expand All @@ -26,9 +28,7 @@ consensus = { path = "../consensus" }
execution = { path = "../execution" }
config = { path = "../config" }


[dependencies.web-sys]
version = "0.3"
features = [
"console",
]

features = ["console", "Storage", "Window"]
2 changes: 1 addition & 1 deletion helios-ts/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<script src="https://cdn.ethers.io/lib/ethers-5.7.umd.min.js"></script>
<script>
const config = {
executionRpc:"http://localhost:9001/proxy",
executionRpc:"http://localhost:9001/proxy",
consensusRpc: "http://localhost:9002/proxy",
checkpoint: "0x3f692997a0c0c66baa549ba3b1897f705b44f36b412fc70ff0e78fdef647bbc3",
};
Expand Down
79 changes: 45 additions & 34 deletions helios-ts/lib.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import init, { Client } from "./pkg/index";


export async function createHeliosProvider(config: Config): Promise<HeliosProvider> {
export async function createHeliosProvider(
config: Config
): Promise<HeliosProvider> {
const wasmData = require("./pkg/index_bg.wasm");
await init(wasmData);
return new HeliosProvider(config);
Expand All @@ -19,8 +20,15 @@ export class HeliosProvider {
const consensusRpc = config.consensusRpc;
const checkpoint = config.checkpoint;
const network = config.network ?? Network.MAINNET;
const dbType = config.dbType ?? "localstorage";

this.#client = new Client(executionRpc, consensusRpc, network, checkpoint);
this.#client = new Client(
executionRpc,
consensusRpc,
network,
checkpoint,
dbType
);
this.#chainId = this.#client.chain_id();
}

Expand All @@ -31,87 +39,91 @@ export class HeliosProvider {
async waitSynced() {
await this.#client.wait_synced();
}

async request(req: Request): Promise<any> {
switch(req.method) {
switch (req.method) {
case "eth_getBalance": {
return this.#client.get_balance(req.params[0], req.params[1]);
};
}
case "eth_chainId": {
return this.#chainId;
};
}
case "eth_blockNumber": {
return this.#client.get_block_number();
};
}
case "eth_getTransactionByHash": {
let tx = await this.#client.get_transaction_by_hash(req.params[0]);
return mapToObj(tx);
};
}
case "eth_getTransactionCount": {
return this.#client.get_transaction_count(req.params[0], req.params[1]);
};
}
case "eth_getBlockTransactionCountByHash": {
return this.#client.get_block_transaction_count_by_hash(req.params[0]);
};
}
case "eth_getBlockTransactionCountByNumber": {
return this.#client.get_block_transaction_count_by_number(req.params[0]);
};
return this.#client.get_block_transaction_count_by_number(
req.params[0]
);
}
case "eth_getCode": {
return this.#client.get_code(req.params[0], req.params[1]);
};
}
case "eth_call": {
return this.#client.call(req.params[0], req.params[1]);
};
}
case "eth_estimateGas": {
return this.#client.estimate_gas(req.params[0]);
};
}
case "eth_gasPrice": {
return this.#client.gas_price();
};
}
case "eth_maxPriorityFeePerGas": {
return this.#client.max_priority_fee_per_gas();
};
}
case "eth_sendRawTransaction": {
return this.#client.send_raw_transaction(req.params[0]);
};
}
case "eth_getTransactionReceipt": {
return this.#client.get_transaction_receipt(req.params[0]);
};
}
case "eth_getLogs": {
return this.#client.get_logs(req.params[0]);
};
}
case "net_version": {
return this.#chainId;
};
}
case "eth_getBlockByNumber": {
return this.#client.get_block_by_number(req.params[0], req.params[1]);
};
}
default: {
throw `method not implemented: ${req.method}`;
};
}
}
}
}

export type Config = {
executionRpc: string,
consensusRpc?: string,
checkpoint?: string,
network?: Network,
}
executionRpc: string;
consensusRpc?: string;
checkpoint?: string;
network?: Network;
/** Where to cache checkpoints, default to "localstorage" */
dbType?: "localstorage" | "config";
};

export enum Network {
MAINNET = "mainnet",
GOERLI = "goerli",
}

type Request = {
method: string,
params: any[],
}
method: string;
params: any[];
};

function mapToObj(map: Map<any, any> | undefined): Object | undefined {
if(!map) return undefined;
if (!map) return undefined;

return Array.from(map).reduce((obj: any, [key, value]) => {
if (value !== undefined) {
Expand All @@ -121,4 +133,3 @@ function mapToObj(map: Map<any, any> | undefined): Object | undefined {
return obj;
}, {});
}

44 changes: 41 additions & 3 deletions helios-ts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,60 @@ extern crate web_sys;
use std::str::FromStr;

use common::types::BlockTag;
use consensus::database::ConfigDB;
use consensus::database::{ConfigDB, Database};
use ethers::types::{Address, Filter, H256};
use execution::types::CallOpts;
use eyre::Result;

use wasm_bindgen::prelude::*;

use config::{networks, Config};

use crate::storage::LocalStorageDB;

pub mod storage;

#[allow(unused_macros)]
macro_rules! log {
( $( $t:tt )* ) => {
web_sys::console::log_1(&format!( $( $t )* ).into());
}
}

#[derive(Clone)]
pub enum DatabaseType {
Memory(ConfigDB),
LocalStorage(LocalStorageDB),
}

impl Database for DatabaseType {
fn new(config: &Config) -> Result<Self> {
// Implement this method based on the behavior of ConfigDB and LocalStorageDB
match config.database_type.as_deref() {
Some("config") => Ok(DatabaseType::Memory(ConfigDB::new(config)?)),
Some("localstorage") => Ok(DatabaseType::LocalStorage(LocalStorageDB::new(config)?)),
_ => Ok(DatabaseType::Memory(ConfigDB::new(config)?)),
}
}

fn load_checkpoint(&self) -> Result<Vec<u8>> {
match self {
DatabaseType::Memory(db) => db.load_checkpoint(),
DatabaseType::LocalStorage(db) => db.load_checkpoint(),
}
}

fn save_checkpoint(&self, checkpoint: &[u8]) -> Result<()> {
match self {
DatabaseType::Memory(db) => db.save_checkpoint(checkpoint),
DatabaseType::LocalStorage(db) => db.save_checkpoint(checkpoint),
}
}
}

#[wasm_bindgen]
pub struct Client {
inner: client::Client<ConfigDB>,
inner: client::Client<DatabaseType>,
chain_id: u64,
}

Expand All @@ -33,6 +69,7 @@ impl Client {
consensus_rpc: Option<String>,
network: String,
checkpoint: Option<String>,
db_type: String,
) -> Self {
console_error_panic_hook::set_once();

Expand Down Expand Up @@ -62,10 +99,11 @@ impl Client {
chain: base.chain,
forks: base.forks,

database_type: Some(db_type),
..Default::default()
};

let inner: client::Client<ConfigDB> =
let inner: client::Client<DatabaseType> =
client::ClientBuilder::new().config(config).build().unwrap();

Self { inner, chain_id }
Expand Down
49 changes: 49 additions & 0 deletions helios-ts/src/storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
extern crate console_error_panic_hook;
extern crate web_sys;

use config::Config;
use consensus::database::Database;
use eyre::Result;
use wasm_bindgen::prelude::*;

#[derive(Clone)]
pub struct LocalStorageDB;

impl Database for LocalStorageDB {
fn new(_config: &Config) -> Result<Self> {
console_error_panic_hook::set_once();
let window = web_sys::window().unwrap();
if let Ok(Some(_local_storage)) = window.local_storage() {
return Ok(Self {});
}

eyre::bail!("local_storage not available")
}

fn load_checkpoint(&self) -> Result<Vec<u8>> {
let window = web_sys::window().unwrap();
if let Ok(Some(local_storage)) = window.local_storage() {
let checkpoint = local_storage.get_item("checkpoint");
if let Ok(Some(checkpoint)) = checkpoint {
let checkpoint = checkpoint.strip_prefix("0x").unwrap_or(&checkpoint);
return hex::decode(checkpoint)
.map_err(|_| eyre::eyre!("Failed to decode checkpoint"));
}
eyre::bail!("checkpoint not found")
}

eyre::bail!("local_storage not available")
}

fn save_checkpoint(&self, checkpoint: &[u8]) -> Result<()> {
let window = web_sys::window().unwrap();
if let Ok(Some(local_storage)) = window.local_storage() {
local_storage
.set_item("checkpoint", &hex::encode(checkpoint))
.unwrap_throw();
return Ok(());
}

eyre::bail!("local_storage not available")
}
}
8 changes: 4 additions & 4 deletions helios-ts/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module.exports = {
rules: [
{
test: /\.ts?$/,
use: 'ts-loader',
use: "ts-loader",
exclude: /node_modules/,
},
{
Expand All @@ -17,16 +17,16 @@ module.exports = {
],
},
resolve: {
extensions: ['.ts', '.js'],
extensions: [".ts", ".js"],
},
output: {
filename: "lib.js",
globalObject: 'this',
globalObject: "this",
path: path.resolve(__dirname, "dist"),
library: {
name: "helios",
type: "umd",
}
},
},
experiments: {
asyncWebAssembly: true,
Expand Down

0 comments on commit 63dc560

Please sign in to comment.