Skip to content
Open
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
Binary file not shown.
22 changes: 17 additions & 5 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ macro_rules! jsonrpc {
}}
}

#[allow(clippy::upper_case_acronyms)]
pub trait RPC: Clone + Send + Sync {
fn get_live_cell(&self, out_point: &OutPoint, with_data: bool) -> Rpc<CellWithStatus>;
fn get_cells(
&self,
search_key: SearchKey,
limit: u32,
cursor: Option<JsonBytes>,
) -> Rpc<Pagination<Cell>>;
}

#[derive(Clone)]
pub struct RpcClient {
raw: Client,
Expand All @@ -66,9 +77,10 @@ pub struct RpcClient {
}

impl RpcClient {
pub fn new(ckb_uri: &str, indexer_uri: &str) -> Self {
pub fn new(ckb_uri: &str, indexer_uri: Option<&str>) -> Self {
let indexer_uri = Url::parse(indexer_uri.unwrap_or(ckb_uri))
.expect("ckb uri, e.g. \"http://127.0.0.1:8116\"");
let ckb_uri = Url::parse(ckb_uri).expect("ckb uri, e.g. \"http://127.0.0.1:8114\"");
let indexer_uri = Url::parse(indexer_uri).expect("ckb uri, e.g. \"http://127.0.0.1:8116\"");

RpcClient {
raw: Client::new(),
Expand All @@ -79,8 +91,8 @@ impl RpcClient {
}
}

impl RpcClient {
pub fn get_live_cell(&self, out_point: &OutPoint, with_data: bool) -> Rpc<CellWithStatus> {
impl RPC for RpcClient {
fn get_live_cell(&self, out_point: &OutPoint, with_data: bool) -> Rpc<CellWithStatus> {
jsonrpc!(
"get_live_cell",
Target::CKB,
Expand All @@ -92,7 +104,7 @@ impl RpcClient {
.boxed()
}

pub fn get_cells(
fn get_cells(
&self,
search_key: SearchKey,
limit: u32,
Expand Down
52 changes: 34 additions & 18 deletions src/decoder/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ use ckb_sdk::{constants::TYPE_ID_CODE_HASH, traits::CellQueryOptions};
use ckb_types::{
core::ScriptHashType,
packed::{OutPoint, Script},
prelude::{Builder, Entity, Pack},
prelude::{Builder, Entity, Pack, Unpack},
H256,
};
use serde_json::Value;
use spore_types::{generated::spore::ClusterData, SporeData};

use crate::{
client::RpcClient,
client::RPC,
types::{
ClusterDescriptionField, DOBDecoderFormat, DecoderLocationType, Error, ScriptId, Settings,
},
Expand Down Expand Up @@ -78,11 +78,11 @@ pub fn decode_spore_data(spore_data: &[u8]) -> Result<(Value, String), Error> {
}

// search on-chain spore cell and return its content field, which represents dob content
pub async fn fetch_dob_content(
rpc: &RpcClient,
pub async fn fetch_dob_content<T: RPC>(
rpc: &T,
settings: &Settings,
spore_id: [u8; 32],
) -> Result<((Value, String), [u8; 32]), Error> {
) -> Result<((Value, String), [u8; 32], H256), Error> {
let mut spore_cell = None;
for spore_search_option in build_batch_search_options(spore_id, &settings.available_spores) {
spore_cell = rpc
Expand All @@ -99,14 +99,25 @@ pub async fn fetch_dob_content(
let Some(spore_cell) = spore_cell else {
return Err(Error::SporeIdNotFound);
};
extract_dob_information(
spore_cell.output_data.unwrap_or_default().as_bytes(),
spore_cell.output.type_.unwrap().into(),
&settings.protocol_versions,
)
}

#[allow(clippy::type_complexity)]
pub fn extract_dob_information(
output_data: &[u8],
spore_type: Script,
protocol_versions: &[String],
) -> Result<((Value, String), [u8; 32], H256), Error> {
let molecule_spore_data =
SporeData::from_compatible_slice(spore_cell.output_data.unwrap_or_default().as_bytes())
.map_err(|_| Error::SporeDataUncompatible)?;
SporeData::from_compatible_slice(output_data).map_err(|_| Error::SporeDataUncompatible)?;
let content_type = String::from_utf8(molecule_spore_data.content_type().raw_data().to_vec())
.map_err(|_| Error::SporeDataContentTypeUncompatible)?;
if !content_type.is_empty()
&& !settings
.protocol_versions
&& !protocol_versions
.iter()
.any(|version| content_type.starts_with(version))
{
Expand All @@ -118,12 +129,17 @@ pub async fn fetch_dob_content(
.ok_or(Error::ClusterIdNotSet)?
.raw_data();
let dob_content = decode_spore_data(&molecule_spore_data.content().raw_data())?;
Ok((dob_content, cluster_id.to_vec().try_into().unwrap()))
let spore_type_hash = spore_type.calc_script_hash().unpack();
Ok((
dob_content,
cluster_id.to_vec().try_into().unwrap(),
spore_type_hash,
))
}

// search on-chain cluster cell and return its description field, which contains dob metadata
pub async fn fetch_dob_metadata(
rpc: &RpcClient,
pub async fn fetch_dob_metadata<T: RPC>(
rpc: &T,
settings: &Settings,
cluster_id: [u8; 32],
) -> Result<ClusterDescriptionField, Error> {
Expand Down Expand Up @@ -154,8 +170,8 @@ pub async fn fetch_dob_metadata(
}

// search on-chain decoder cell, deployed with type_id feature enabled
async fn fetch_decoder_binary(
rpc: &RpcClient,
async fn fetch_decoder_binary<T: RPC>(
rpc: &T,
decoder_search_option: CellQueryOptions,
) -> Result<Vec<u8>, Error> {
let decoder_cell = rpc
Expand All @@ -174,8 +190,8 @@ async fn fetch_decoder_binary(
}

// search on-chain decoder cell, directly by its tx_hash and out_index
async fn fetch_decoder_binary_directly(
rpc: &RpcClient,
async fn fetch_decoder_binary_directly<T: RPC>(
rpc: &T,
tx_hash: H256,
out_index: u32,
) -> Result<Vec<u8>, Error> {
Expand All @@ -192,8 +208,8 @@ async fn fetch_decoder_binary_directly(
Ok(decoder_binary.as_bytes().to_vec())
}

pub async fn parse_decoder_path(
rpc: &RpcClient,
pub async fn parse_decoder_path<T: RPC>(
rpc: &T,
decoder: &DOBDecoderFormat,
settings: &Settings,
) -> Result<PathBuf, Error> {
Expand Down
56 changes: 37 additions & 19 deletions src/decoder/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use ckb_types::H256;
use serde_json::Value;

use crate::{
client::RpcClient,
client::RPC,
types::{
ClusterDescriptionField, DOBClusterFormatV0, DOBClusterFormatV1, Dob, Error, Settings,
StandardDOBOutput,
Expand All @@ -11,17 +12,14 @@ use crate::{
pub(crate) mod helpers;
use helpers::*;

pub struct DOBDecoder {
rpc: RpcClient,
pub struct DOBDecoder<T: RPC + 'static> {
rpc: T,
settings: Settings,
}

impl DOBDecoder {
pub fn new(settings: Settings) -> Self {
Self {
rpc: RpcClient::new(&settings.ckb_rpc, &settings.ckb_rpc),
settings,
}
impl<T: RPC> DOBDecoder<T> {
pub fn new(rpc: T, settings: Settings) -> Self {
Self { rpc, settings }
}

pub fn protocol_versions(&self) -> Vec<String> {
Expand All @@ -35,27 +33,34 @@ impl DOBDecoder {
pub async fn fetch_decode_ingredients(
&self,
spore_id: [u8; 32],
) -> Result<((Value, String), ClusterDescriptionField), Error> {
let (content, cluster_id) = fetch_dob_content(&self.rpc, &self.settings, spore_id).await?;
) -> Result<((Value, String), ClusterDescriptionField, H256), Error> {
let (content, cluster_id, type_hash) =
fetch_dob_content(&self.rpc, &self.settings, spore_id).await?;
let dob_metadata = fetch_dob_metadata(&self.rpc, &self.settings, cluster_id).await?;
Ok((content, dob_metadata))
Ok((content, dob_metadata, type_hash))
}

// decode DNA under target spore_id
pub async fn decode_dna(
&self,
dna: &str,
dob_metadata: ClusterDescriptionField,
spore_type_hash: H256,
) -> Result<String, Error> {
let dob = dob_metadata.unbox_dob()?;
match dob {
Dob::V0(dob0) => self.decode_dob0_dna(dna, dob0).await,
Dob::V1(dob1) => self.decode_dob1_dna(dna, dob1).await,
Dob::V0(dob0) => self.decode_dob0_dna(dna, dob0, spore_type_hash).await,
Dob::V1(dob1) => self.decode_dob1_dna(dna, dob1, spore_type_hash).await,
}
}

// decode specificly for objects under DOB/0 protocol
async fn decode_dob0_dna(&self, dna: &str, dob0: &DOBClusterFormatV0) -> Result<String, Error> {
async fn decode_dob0_dna(
&self,
dna: &str,
dob0: &DOBClusterFormatV0,
spore_type_hash: H256,
) -> Result<String, Error> {
let decoder_path = parse_decoder_path(&self.rpc, &dob0.decoder, &self.settings).await?;
let pattern = match &dob0.pattern {
Value::String(string) => string.to_owned(),
Expand All @@ -65,6 +70,9 @@ impl DOBDecoder {
let (exit_code, outputs) = crate::vm::execute_riscv_binary(
&decoder_path.to_string_lossy(),
vec![dna.to_owned().into(), pattern.into()],
spore_type_hash,
self.rpc.clone(),
&self.settings,
)
.map_err(|_| Error::DecoderExecutionError)?;
#[cfg(feature = "render_debug")]
Expand All @@ -82,7 +90,12 @@ impl DOBDecoder {
}

// decode specificly for objects under DOB/1 protocol
async fn decode_dob1_dna(&self, dna: &str, dob1: &DOBClusterFormatV1) -> Result<String, Error> {
async fn decode_dob1_dna(
&self,
dna: &str,
dob1: &DOBClusterFormatV1,
spore_type_hash: H256,
) -> Result<String, Error> {
let mut output = Option::<Vec<StandardDOBOutput>>::None;
for (i, value) in dob1.decoders.iter().enumerate() {
let decoder_path =
Expand All @@ -103,9 +116,14 @@ impl DOBDecoder {
} else {
vec![dna.to_owned().into(), pattern.into()]
};
let (exit_code, outputs) =
crate::vm::execute_riscv_binary(&decoder_path.to_string_lossy(), args)
.map_err(|_| Error::DecoderExecutionError)?;
let (exit_code, outputs) = crate::vm::execute_riscv_binary(
&decoder_path.to_string_lossy(),
args,
spore_type_hash.clone(),
self.rpc.clone(),
&self.settings,
)
.map_err(|_| Error::DecoderExecutionError)?;
#[cfg(feature = "render_debug")]
{
println!("\n-------- DOB/1 DECODE RESULT ({i} => {exit_code}) ---------");
Expand Down
4 changes: 3 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::fs;

use client::RpcClient;
use jsonrpsee::{server::ServerBuilder, tracing};
use server::DecoderRpcServer;
use tracing_subscriber::EnvFilter;
Expand Down Expand Up @@ -27,7 +28,8 @@ async fn main() {
);
let rpc_server_address = settings.rpc_server_address.clone();
let cache_expiration = settings.dobs_cache_expiration_sec;
let decoder = decoder::DOBDecoder::new(settings);
let rpc = RpcClient::new(&settings.ckb_rpc, None);
let decoder = decoder::DOBDecoder::new(rpc, settings);

tracing::info!("running decoder server at {}", rpc_server_address);
let http_server = ServerBuilder::new()
Expand Down
19 changes: 12 additions & 7 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use jsonrpsee::{proc_macros::rpc, tracing, types::ErrorCode};
use serde::Serialize;
use serde_json::Value;

use crate::client::RPC;
use crate::decoder::DOBDecoder;
use crate::types::Error;

Expand All @@ -29,13 +30,13 @@ trait DecoderRpc {
async fn batch_decode(&self, hexed_spore_ids: Vec<String>) -> Result<Vec<String>, ErrorCode>;
}

pub struct DecoderStandaloneServer {
decoder: DOBDecoder,
pub struct DecoderStandaloneServer<T: RPC + 'static> {
decoder: DOBDecoder<T>,
cache_expiration: u64,
}

impl DecoderStandaloneServer {
pub fn new(decoder: DOBDecoder, cache_expiration: u64) -> Self {
impl<T: RPC> DecoderStandaloneServer<T> {
pub fn new(decoder: DOBDecoder<T>, cache_expiration: u64) -> Self {
Self {
decoder,
cache_expiration,
Expand All @@ -47,15 +48,19 @@ impl DecoderStandaloneServer {
spore_id: [u8; 32],
cache_path: PathBuf,
) -> Result<(String, Value), Error> {
let ((content, dna), metadata) = self.decoder.fetch_decode_ingredients(spore_id).await?;
let render_output = self.decoder.decode_dna(&dna, metadata).await?;
let ((content, dna), metadata, spore_type_hash) =
self.decoder.fetch_decode_ingredients(spore_id).await?;
let render_output = self
.decoder
.decode_dna(&dna, metadata, spore_type_hash)
.await?;
write_dob_to_cache(&render_output, &content, cache_path, self.cache_expiration)?;
Ok((render_output, content))
}
}

#[async_trait]
impl DecoderRpcServer for DecoderStandaloneServer {
impl<T: RPC> DecoderRpcServer for DecoderStandaloneServer<T> {
async fn protocol_versions(&self) -> Vec<String> {
self.decoder.protocol_versions()
}
Expand Down
15 changes: 9 additions & 6 deletions src/tests/dob0/decoder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ckb_types::{h256, H256};
use serde_json::{json, Value};

use crate::client::RpcClient;
use crate::decoder::DOBDecoder;
use crate::tests::prepare_settings;
use crate::types::{
Expand Down Expand Up @@ -85,13 +86,14 @@ fn generate_example_dob_ingredients(onchain_decoder: bool) -> (Value, ClusterDes
#[tokio::test]
async fn test_fetch_and_decode_unicorn_dna() {
let settings = prepare_settings("text/plain");
let decoder = DOBDecoder::new(settings);
let ((_, dna), dob_metadata) = decoder
let rpc = RpcClient::new(&settings.ckb_rpc, None);
let decoder = DOBDecoder::new(rpc, settings);
let ((_, dna), dob_metadata, type_hash) = decoder
.fetch_decode_ingredients(UNICORN_SPORE_ID.into())
.await
.expect("fetch");
let render_result = decoder
.decode_dna(&dna, dob_metadata)
.decode_dna(&dna, dob_metadata, type_hash)
// array type
.await
.expect("decode");
Expand All @@ -116,13 +118,14 @@ fn test_unicorn_json_serde() {
#[tokio::test]
async fn test_fetch_and_decode_example_dna() {
let settings = prepare_settings("text/plain");
let decoder = DOBDecoder::new(settings);
let ((_, dna), dob_metadata) = decoder
let rpc = RpcClient::new(&settings.ckb_rpc, None);
let decoder = DOBDecoder::new(rpc, settings);
let ((_, dna), dob_metadata, type_hash) = decoder
.fetch_decode_ingredients(EXAMPLE_SPORE_ID.into())
.await
.expect("fetch");
let render_result = decoder
.decode_dna(&dna, dob_metadata)
.decode_dna(&dna, dob_metadata, type_hash)
// array type
.await
.expect("decode");
Expand Down
Loading