Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 184 additions & 59 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ members = [
"light-client-bin",
"./wasm/light-client-wasm",
"./wasm/light-client-db-worker",
"./wasm/light-client-db-common",
"./wasm/light-client-db-common",
"light-client-rpc",
]

resolver = "2"
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fmt:
clippy:
# cargo clippy --workspace --locked -- --deny warnings
# Run clippy for wasm targets
cargo clippy --target wasm32-unknown-unknown -p light-client-wasm -p ckb-light-client-lib -p light-client-db-common -p light-client-db-worker --locked -- --deny warnings
cargo clippy --target wasm32-unknown-unknown -p light-client-wasm -p ckb-light-client-lib -p light-client-db-common -p light-client-db-worker -p ckb-light-client-rpc --locked -- --deny warnings
# Run clippy for native targets
cargo clippy -p ckb-light-client --locked -- --deny warnings
build:
Expand Down
46 changes: 25 additions & 21 deletions light-client-bin/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,57 +1,61 @@
[package]
name = "ckb-light-client"
version = "0.5.4"
authors = ["Nervos Core Dev <dev@nervos.org>"]
edition = "2021"
license = "MIT"
description = "A CKB light client based on FlyClient."
edition = "2021"
homepage = "https://github.com/nervosnetwork/ckb-light-client"
license = "MIT"
name = "ckb-light-client"
repository = "https://github.com/nervosnetwork/ckb-light-client"
version = "0.5.4"

[dependencies]
ckb-async-runtime = "1"
ckb-chain-spec = "1"
ckb-jsonrpc-types = "1"
ckb-network = "1"
ckb-stop-handler = "1"
ckb-resource = "1"
ckb-jsonrpc-types = "1"
ckb-types = "1"
ckb-traits = "1"
ckb-stop-handler = "1"
ckb-systemtime = "1"
ckb-traits = "1"
ckb-types = "1"

ckb-light-client-lib = { path = "../light-client-lib" }
clap = { version = "4", features = ["cargo"] }
log = "0.4"
ctrlc = { version = "3.2.1", features = ["termination"] }
ckb-light-client-lib = {path = "../light-client-lib", default-features = false}
ckb-light-client-rpc = {path = "../light-client-rpc", default-features = false}
clap = {version = "4", features = ["cargo"]}
ctrlc = {version = "3.2.1", features = ["termination"]}
jsonrpc-core = "18.0"
jsonrpc-derive = "18.0"
jsonrpc-http-server = "18.0"
jsonrpc-server-utils = "18.0"
rocksdb = { package = "ckb-rocksdb", version = "=0.21.1", features = [
"snappy",
], default-features = false }
env_logger = "0.11"
log = "0.4"
rocksdb = {package = "ckb-rocksdb", version = "=0.21.1", features = [
"snappy",
], default-features = false, optional = true}
rusqlite = {version = "0.38.0", features = ["bundled"], optional = true}

anyhow = "1.0.56"
env_logger = "0.11"

[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = "0.6"

[build-dependencies]
vergen-gitcl = { version = "1", default-features = false }
chrono = "0.4"
vergen-gitcl = {version = "1", default-features = false}

[dev-dependencies]
rand = "0.8"
serde_json = "1.0"
tempfile = "3.0"

[features]
default = []
portable = ["rocksdb/portable"]
default = ["rocksdb"]
march-native = ["rocksdb/march-native"]

portable = ["rocksdb/portable"]
rocksdb = ["dep:rocksdb", "ckb-light-client-lib/rocksdb", "ckb-light-client-rpc/rocksdb"]
rusqlite = ["ckb-light-client-lib/rusqlite", "ckb-light-client-rpc/rusqlite", "dep:rusqlite"]
# [profile.release]
# overflow-checks = true

[badges]
maintenance = { status = "experimental" }
maintenance = {status = "experimental"}
6 changes: 6 additions & 0 deletions light-client-bin/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
mod cli;
mod rpc;
#[cfg(any(
all(feature = "rocksdb", feature = "rusqlite"),
not(any(feature = "rocksdb", feature = "rusqlite"))
))]
compile_error!("Exact one of features `rocksdb` and `rusqlite` can be selected");

mod subcmds;

#[cfg(test)]
Expand Down
281 changes: 281 additions & 0 deletions light-client-bin/src/rpc/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
use ckb_light_client_lib::{
protocols::PendingTxs,
storage::{
db::{
StorageGeneralOperations, StorageGetPinnedRelatedOperations, StorageHighLevelOperations,
},
Storage,
},
types::RwLock,
};
use ckb_light_client_rpc::{
BlockFilterRpcImpl, BlockFilterRpcMethods, ChainRpcImpl, ChainRpcMethods, NetRpcImpl,
NetRpcMethods, TransactionRpcImpl, TransactionRpcMethods,
};
use jsonrpc_core::{Error, IoHandler, Result};
use jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, Server, ServerBuilder};
use std::{net::ToSocketAddrs, sync::Arc};

use ckb_chain_spec::consensus::Consensus;
use ckb_jsonrpc_types::{BlockView, EstimateCycles, HeaderView, JsonBytes, Transaction, Uint32};
use ckb_light_client_lib::{
protocols::Peers,
service::{
Cell, CellsCapacity, FetchStatus, LocalNode, Order, Pagination, RemoteNode, ScriptStatus,
SearchKey, SetScriptsCommand, TransactionWithStatus, Tx,
},
storage::StorageWithChainData,
};
use ckb_network::NetworkController;
use ckb_traits::CellDataProvider;
use ckb_types::H256;
use jsonrpc_derive::rpc;

#[rpc(server)]
pub trait BlockFilterRpc {
/// curl http://localhost:9000/ -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method":"set_scripts", "params": [{"script": {"code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type", "args": "0x50878ce52a68feb47237c29574d82288f58b5d21"}, "block_number": "0x59F74D"}], "id": 1}'
#[rpc(name = "set_scripts")]
fn set_scripts(
&self,
scripts: Vec<ScriptStatus>,
command: Option<SetScriptsCommand>,
) -> Result<()>;

#[rpc(name = "get_scripts")]
fn get_scripts(&self) -> Result<Vec<ScriptStatus>>;

#[rpc(name = "get_cells")]
fn get_cells(
&self,
search_key: SearchKey,
order: Order,
limit: Uint32,
after: Option<JsonBytes>,
) -> Result<Pagination<Cell>>;

#[rpc(name = "get_transactions")]
fn get_transactions(
&self,
search_key: SearchKey,
order: Order,
limit: Uint32,
after: Option<JsonBytes>,
) -> Result<Pagination<Tx>>;

#[rpc(name = "get_cells_capacity")]
fn get_cells_capacity(&self, search_key: SearchKey) -> Result<CellsCapacity>;
}

#[rpc(server)]
pub trait TransactionRpc {
#[rpc(name = "send_transaction")]
fn send_transaction(&self, tx: Transaction) -> Result<H256>;

#[rpc(name = "get_transaction")]
fn get_transaction(&self, tx_hash: H256) -> Result<TransactionWithStatus>;

#[rpc(name = "fetch_transaction")]
fn fetch_transaction(&self, tx_hash: H256) -> Result<FetchStatus<TransactionWithStatus>>;
}

#[rpc(server)]
pub trait ChainRpc {
#[rpc(name = "get_tip_header")]
fn get_tip_header(&self) -> Result<HeaderView>;

#[rpc(name = "get_genesis_block")]
fn get_genesis_block(&self) -> Result<BlockView>;

#[rpc(name = "get_header")]
fn get_header(&self, block_hash: H256) -> Result<Option<HeaderView>>;

#[rpc(name = "fetch_header")]
fn fetch_header(
&self,
block_hash: H256,
) -> Result<ckb_light_client_lib::service::FetchStatus<HeaderView>>;

#[rpc(name = "estimate_cycles")]
fn estimate_cycles(&self, tx: Transaction) -> Result<EstimateCycles>;
}

#[rpc(server)]
pub trait NetRpc {
#[rpc(name = "local_node_info")]
fn local_node_info(&self) -> Result<LocalNode>;

#[rpc(name = "get_peers")]
fn get_peers(&self) -> Result<Vec<RemoteNode>>;
}

impl<S: StorageHighLevelOperations + Send + Sync + Clone + 'static> BlockFilterRpc
for BlockFilterRpcImpl<S>
{
fn set_scripts(
&self,
scripts: Vec<ScriptStatus>,
command: Option<SetScriptsCommand>,
) -> Result<()> {
BlockFilterRpcMethods::set_scripts(self, scripts, command)
.map_err(|e| Error::invalid_params(e.to_string()))
}

fn get_scripts(&self) -> Result<Vec<ScriptStatus>> {
BlockFilterRpcMethods::get_scripts(self).map_err(|e| Error::invalid_params(e.to_string()))
}

fn get_cells(
&self,
search_key: SearchKey,
order: Order,
limit: Uint32,
after: Option<JsonBytes>,
) -> Result<Pagination<Cell>> {
BlockFilterRpcMethods::get_cells(self, search_key, order, limit, after)
.map_err(|e| Error::invalid_params(e.to_string()))
}

fn get_transactions(
&self,
search_key: SearchKey,
order: Order,
limit: Uint32,
after: Option<JsonBytes>,
) -> Result<Pagination<Tx>> {
BlockFilterRpcMethods::get_transactions(self, search_key, order, limit, after)
.map_err(|e| Error::invalid_params(e.to_string()))
}

fn get_cells_capacity(&self, search_key: SearchKey) -> Result<CellsCapacity> {
BlockFilterRpcMethods::get_cells_capacity(self, search_key)
.map_err(|e| Error::invalid_params(e.to_string()))
}
}

impl<
S: StorageHighLevelOperations
+ StorageGetPinnedRelatedOperations
+ CellDataProvider
+ Send
+ Sync
+ Clone
+ 'static,
> TransactionRpc for TransactionRpcImpl<S>
{
fn send_transaction(&self, tx: Transaction) -> Result<H256> {
TransactionRpcMethods::send_transaction(self, tx)
.map_err(|e| Error::invalid_params(e.to_string()))
}

fn get_transaction(&self, tx_hash: H256) -> Result<TransactionWithStatus> {
TransactionRpcMethods::get_transaction(self, tx_hash)
.map_err(|e| Error::invalid_params(e.to_string()))
}

fn fetch_transaction(&self, tx_hash: H256) -> Result<FetchStatus<TransactionWithStatus>> {
TransactionRpcMethods::fetch_transaction(self, tx_hash)
.map_err(|e| Error::invalid_params(e.to_string()))
}
}

impl<
S: StorageHighLevelOperations
+ StorageGeneralOperations
+ StorageGetPinnedRelatedOperations
+ CellDataProvider
+ Send
+ Sync
+ Clone
+ 'static,
> ChainRpc for ChainRpcImpl<S>
{
fn get_tip_header(&self) -> Result<HeaderView> {
ChainRpcMethods::get_tip_header(self).map_err(|e| Error::invalid_params(e.to_string()))
}

fn get_genesis_block(&self) -> Result<BlockView> {
ChainRpcMethods::get_genesis_block(self).map_err(|e| Error::invalid_params(e.to_string()))
}

fn get_header(&self, block_hash: H256) -> Result<Option<HeaderView>> {
ChainRpcMethods::get_header(self, block_hash)
.map_err(|e| Error::invalid_params(e.to_string()))
}

fn fetch_header(
&self,
block_hash: H256,
) -> Result<ckb_light_client_lib::service::FetchStatus<HeaderView>> {
ChainRpcMethods::fetch_header(self, block_hash)
.map_err(|e| Error::invalid_params(e.to_string()))
}

fn estimate_cycles(&self, tx: Transaction) -> Result<EstimateCycles> {
ChainRpcMethods::estimate_cycles(self, tx).map_err(|e| Error::invalid_params(e.to_string()))
}
}

impl NetRpc for NetRpcImpl {
fn local_node_info(&self) -> Result<LocalNode> {
NetRpcMethods::local_node_info(self).map_err(|e| Error::invalid_params(e.to_string()))
}

fn get_peers(&self) -> Result<Vec<RemoteNode>> {
NetRpcMethods::get_peers(self).map_err(|e| Error::invalid_params(e.to_string()))
}
}

pub struct Service {
listen_address: String,
}

impl Service {
pub fn new(listen_address: &str) -> Self {
Self {
listen_address: listen_address.to_string(),
}
}

pub fn start(
&self,
network_controller: NetworkController,
storage: Storage,
peers: Arc<Peers>,
pending_txs: Arc<RwLock<PendingTxs>>,
consensus: Consensus,
) -> Server {
let mut io_handler = IoHandler::new();
let swc = StorageWithChainData::new(storage, Arc::clone(&peers), Arc::clone(&pending_txs));
let consensus = Arc::new(consensus);
let block_filter_rpc_impl = BlockFilterRpcImpl { swc: swc.clone() };
let chain_rpc_impl = ChainRpcImpl {
swc: swc.clone(),
consensus: Arc::clone(&consensus),
};
let transaction_rpc_impl = TransactionRpcImpl { swc, consensus };
let net_rpc_impl = NetRpcImpl {
network_controller,
peers,
};
io_handler.extend_with(block_filter_rpc_impl.to_delegate());
io_handler.extend_with(chain_rpc_impl.to_delegate());
io_handler.extend_with(transaction_rpc_impl.to_delegate());
io_handler.extend_with(net_rpc_impl.to_delegate());

ServerBuilder::new(io_handler)
.cors(DomainsValidation::AllowOnly(vec![
AccessControlAllowOrigin::Null,
AccessControlAllowOrigin::Any,
]))
.health_api(("/ping", "ping"))
.start_http(
&self
.listen_address
.to_socket_addrs()
.expect("config listen_address parsed")
.next()
.expect("config listen_address parsed"),
)
.expect("Start Jsonrpc HTTP service")
}
}
Loading