1. 初始Prompt
在测试网 (Pudge)上,
Cluster的 tx_hash : 0xcebb174d6e300e26074aea2f5dbd7f694bb4fe3de52b6dfe205e54f90164510a
Spore的 tx_hash : 0x5e8d2a517d50fd4bb4d01737a7952a1f1d35c8afc77240695bb569cd7d9d5a1f
一个具体的DOB包括含具体id的cluster和含具体id的spore
请编写一个DOBLock,UTXO上的CKB只能由某个DOB解锁
#![no_std]
#![no_main]
#![feature(lang_items)]
#![feature(alloc_error_handler)]
#![feature(panic_info_message)]
// Import CKB syscalls and high-level APIs
use ckb_std::{
ckb_constants::Source,
ckb_types::prelude::*,
default_alloc,
entry,
error::SysError,
high_level::{load_script, load_cell_data, load_cell_type, load_cell_type_hash, QueryIter},
};
// Constants that you need to define based on your Spore protocol deployment
// Example: Replace with actual Spore Type Script Code Hash and Hash Type
const SPORE_TYPE_CODE_HASH: [u8; 32] = [0x00, /* ... 30 more bytes ... */ 0x00];
const SPORE_TYPE_HASH_TYPE: u8 = 0x01; // 0x00 for data, 0x01 for type, 0x02 for data1
// Define the expected length of Cluster ID and Spore ID
// Adjust these if your IDs have different lengths
const CLUSTER_ID_LEN: usize = 32;
const SPORE_ID_LEN: usize = 32;
// Error codes
mod error {
use ckb_std::error::SysError;
// See: https://github.com/nervosnetwork/ckb-system-scripts/blob/master/c/error.h
// And: https://github.com/nervosnetwork/ckb-vm/blob/master/versions/VM1/asm/riscv_asm.h
pub const ERROR_ARGS_LEN: i8 = -1;
pub const ERROR_ENCODING: i8 = -2;
pub const ERROR_SYSCALL: i8 = -3;
pub const ERROR_SCRIPT_TOO_LONG: i8 = -21;
pub const ERROR_VALIDATION_FAILURE: i8 = -31;
pub const ERROR_INVALID_MOL_FORMAT: i8 = -41;
pub const ERROR_DOB_NOT_FOUND_IN_INPUTS: i8 = 50;
pub const ERROR_CLUSTER_ID_MISMATCH: i8 = 51;
pub const ERROR_SPORE_ID_MISMATCH: i8 = 52;
pub const ERROR_INVALID_SPORE_CELL_DATA: i8 = 53;
}
// Main entry point
fn main() -> i8 {
// 1. Load current script (the DOBLock)
let script = match load_script() {
Ok(s) => s,
Err(e) => return e.into(),
};
let args: &[u8] = script.args().raw_data();
// 2. Validate arguments length
// Expected: target_cluster_id (32 bytes) + target_spore_id (32 bytes)
if args.len() != CLUSTER_ID_LEN + SPORE_ID_LEN {
return error::ERROR_ARGS_LEN;
}
let target_cluster_id = &args[0..CLUSTER_ID_LEN];
let target_spore_id = &args[CLUSTER_ID_LEN..(CLUSTER_ID_LEN + SPORE_ID_LEN)];
// 3. Iterate through transaction inputs to find the required DOB
let mut dob_found = false;
for i in 0.. {
// Load cell type script from input
match load_cell_type(i, Source::Input) {
Ok(Some(input_type_script)) => {
// Check if this is a Spore Cell
if input_type_script.code_hash().raw_data().as_ref() == SPORE_TYPE_CODE_HASH
&& input_type_script.hash_type().as_slice() == [SPORE_TYPE_HASH_TYPE]
{
let input_spore_id = input_type_script.args().raw_data();
// Check Spore ID
if input_spore_id.as_ref() != target_spore_id {
// Not the target Spore, continue searching
continue;
}
// Spore ID matches, now check Cluster ID from cell data
match load_cell_data(i, Source::Input) {
Ok(spore_cell_data) => {
// TODO: Implement the logic to parse cluster_id from spore_cell_data
// This depends on how cluster_id is stored in your Spore's data structure.
// For example, if cluster_id is the first 32 bytes of the data:
if spore_cell_data.len() >= CLUSTER_ID_LEN {
let input_cluster_id = &spore_cell_data[0..CLUSTER_ID_LEN];
if input_cluster_id == target_cluster_id {
dob_found = true;
break; // DOB found, exit loop
} else {
// Cluster ID mismatch for the target Spore ID
// This might be an error condition depending on your exact requirements
// For now, we'll assume it's just not the DOB we're looking for if cluster_id doesn't match.
// If a Spore ID can only belong to one Cluster, then this is an error.
// return error::ERROR_CLUSTER_ID_MISMATCH;
}
} else {
// Spore cell data is too short to contain a cluster_id
return error::ERROR_INVALID_SPORE_CELL_DATA;
}
}
Err(SysError::IndexOutOfBound) => {
// Should not happen if load_cell_type was successful
break;
}
Err(_) => return error::ERROR_SYSCALL, // Or a more specific error
}
}
}
Ok(None) => {
// No type script for this input, skip
continue;
}
Err(SysError::IndexOutOfBound) => {
// Reached the end of inputs
break;
}
Err(e) => return e.into(), // Other syscall error
}
}
if dob_found {
0 // Success
} else {
error::ERROR_DOB_NOT_FOUND_IN_INPUTS
}
}
// Default allocators and panic handlers for no_std environment
#[global_allocator]
static ALLOC: default_alloc::DefaultAllocator = default_alloc::DefaultAllocator;
#[alloc_error_handler]
fn oom_handler(_layout: core::alloc::Layout) -> ! {
panic!("Out of memory");
}
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
if let Some(s) = info.message() {
ckb_std::syscalls::debug(alloc::format!("panic: {:?}", s).as_str());
} else {
ckb_std::syscalls::debug("panic occurred");
}
ckb_std::syscalls::exit(-1);
}
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
entry!(main);
2. 通用化,不硬编码code_hash
如果想让这个lock更加通用,适用于任意版本的Spore,就不应该硬编码cluster和spore的code_hash,应该把它们放在args或其它字段中,应该怎么更新代码?
#![no_std]
#![no_main]
#![feature(lang_items)]
#![feature(alloc_error_handler)]
#![feature(panic_info_message)]
// Import CKB syscalls and high-level APIs
use ckb_std::{
ckb_constants::Source,
ckb_types::prelude::*,
default_alloc,
entry,
error::SysError,
high_level::{load_script, load_cell_data, load_cell_type, QueryIter},
};
// Define the expected length of Cluster ID and Spore ID
// Adjust these if your IDs have different lengths
const CODE_HASH_LEN: usize = 32;
const HASH_TYPE_LEN: usize = 1;
const CLUSTER_ID_LEN: usize = 32;
const SPORE_ID_LEN: usize = 32;
// Error codes
mod error {
// ... existing code ...
pub const ERROR_ARGS_LEN: i8 = -1;
pub const ERROR_ENCODING: i8 = -2;
pub const ERROR_SYSCALL: i8 = -3;
pub const ERROR_SCRIPT_TOO_LONG: i8 = -21;
pub const ERROR_VALIDATION_FAILURE: i8 = -31;
pub const ERROR_INVALID_MOL_FORMAT: i8 = -41;
pub const ERROR_DOB_NOT_FOUND_IN_INPUTS: i8 = 50;
pub const ERROR_CLUSTER_ID_MISMATCH: i8 = 51;
pub const ERROR_SPORE_ID_MISMATCH: i8 = 52;
pub const ERROR_INVALID_SPORE_CELL_DATA: i8 = 53;
}
// Main entry point
fn main() -> i8 {
// 1. Load current script (the DOBLock)
let script = match load_script() {
Ok(s) => s,
Err(e) => return e.into(),
};
let args: &[u8] = script.args().raw_data();
// 2. Validate arguments length
// Expected: spore_code_hash (32) + spore_hash_type (1) + cluster_id (32) + spore_id (32)
const EXPECTED_ARGS_LEN: usize = CODE_HASH_LEN + HASH_TYPE_LEN + CLUSTER_ID_LEN + SPORE_ID_LEN;
if args.len() != EXPECTED_ARGS_LEN {
return error::ERROR_ARGS_LEN;
}
let mut offset = 0;
let target_spore_type_code_hash = &args[offset..offset + CODE_HASH_LEN];
offset += CODE_HASH_LEN;
let target_spore_type_hash_type = args[offset]; // Read the single byte for hash_type
offset += HASH_TYPE_LEN;
let target_cluster_id = &args[offset..offset + CLUSTER_ID_LEN];
offset += CLUSTER_ID_LEN;
let target_spore_id = &args[offset..offset + SPORE_ID_LEN];
// 3. Iterate through transaction inputs to find the required DOB
let mut dob_found = false;
for i in 0.. {
// Load cell type script from input
match load_cell_type(i, Source::Input) {
Ok(Some(input_type_script)) => {
// Check if this is the target Spore Cell type
if input_type_script.code_hash().raw_data().as_ref() == target_spore_type_code_hash
&& input_type_script.hash_type().as_slice() == [target_spore_type_hash_type]
{
let input_spore_id = input_type_script.args().raw_data();
// Check Spore ID
if input_spore_id.as_ref() != target_spore_id {
// Not the target Spore, continue searching
continue;
}
// Spore ID matches, now check Cluster ID from cell data
match load_cell_data(i, Source::Input) {
Ok(spore_cell_data) => {
// TODO: Implement the logic to parse cluster_id from spore_cell_data
// This depends on how cluster_id is stored in your Spore's data structure.
// For example, if cluster_id is the first CLUSTER_ID_LEN bytes of the data:
if spore_cell_data.len() >= CLUSTER_ID_LEN {
let input_cluster_id = &spore_cell_data[0..CLUSTER_ID_LEN];
if input_cluster_id == target_cluster_id {
dob_found = true;
break; // DOB found, exit loop
} else {
// Cluster ID mismatch for the target Spore ID
// return error::ERROR_CLUSTER_ID_MISMATCH;
}
} else {
// Spore cell data is too short to contain a cluster_id
return error::ERROR_INVALID_SPORE_CELL_DATA;
}
}
Err(SysError::IndexOutOfBound) => {
// Should not happen if load_cell_type was successful
break;
}
Err(_) => return error::ERROR_SYSCALL, // Or a more specific error
}
}
}
Ok(None) => {
// No type script for this input, skip
continue;
}
Err(SysError::IndexOutOfBound) => {
// Reached the end of inputs
break;
}
Err(e) => return e.into(), // Other syscall error
}
}
if dob_found {
0 // Success
} else {
error::ERROR_DOB_NOT_FOUND_IN_INPUTS
}
}
// ... existing allocator and panic handler code ...
#[global_allocator]
static ALLOC: default_alloc::DefaultAllocator = default_alloc::DefaultAllocator;
#[alloc_error_handler]
fn oom_handler(_layout: core::alloc::Layout) -> ! {
panic!("Out of memory");
}
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
if let Some(s) = info.message() {
ckb_std::syscalls::debug(alloc::format!("panic: {:?}", s).as_str());
} else {
ckb_std::syscalls::debug("panic occurred");
}
ckb_std::syscalls::exit(-1);
}
#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
entry!(main);
3. 引入molecule
Nervos官方有一个负责序列化和反序列化的库 molecule,请引入它处理args中的数据,以便让它更容易阅读
// Import CKB standard library
use ckb_std::{
ckb_constants::Source,
ckb_types::core::ScriptHashType,
ckb_types::{bytes::Bytes, prelude::*},
high_level::*,
};
// Import the generated molecule code
// Assuming you named the generated file dob_lock_args_mol.rs and placed it in src
mod dob_lock_args_mol;
use dob_lock_args_mol::DOBLockArgs;
// Import Spore/Cluster related constants or functions if needed
// For example, if Spore ID is stored in a specific part of the data
// or if Cluster ID needs specific parsing from Spore data.
// const SPORE_TYPE_SCRIPT_ARGS_CLUSTER_ID_OFFSET: usize = 32; // Example offset for Cluster ID in Spore type args
// const SPORE_TYPE_SCRIPT_ARGS_CLUSTER_ID_LEN: usize = 32; // Example length for Cluster ID
// Define error codes
#[repr(i8)]
enum Error {
IndexOutOfBound = 1,
ItemMissing,
LengthNotEnough,
Encoding,
// Add more error codes as needed
ArgsInvalid,
SporeCellNotFound,
SporeTypeMismatch,
ClusterIdMismatch,
SporeIdMismatch,
InvalidHashType,
}
impl From<molecule::error::VerificationError> for Error {
fn from(_: molecule::error::VerificationError) -> Self {
Error::Encoding
}
}
fn verify_spore_and_cluster(
target_spore_type_code_hash: &[u8; 32],
target_spore_type_hash_type: ScriptHashType,
target_cluster_id_bytes: &[u8; 32],
target_spore_id_bytes: &[u8; 32],
) -> Result<(), Error> {
let mut spore_found = false;
// Iterate through inputs to find the Spore cell
for i in 0.. {
match load_cell_type(i, Source::Input) {
Ok(Some(script)) => {
// 1. Check if the type script code_hash and hash_type match the target Spore type
if script.code_hash().raw_data().as_ref() == target_spore_type_code_hash
&& script.hash_type().into() == target_spore_type_hash_type
{
// This input cell has the target Spore type script.
// Now, we need to verify Cluster ID and Spore ID.
// 2. Verify Cluster ID
// Cluster ID is typically part of the Spore's type script args or its data.
// This part depends heavily on how your Spore protocol defines Cluster ID association.
// For this example, let's assume Cluster ID is the first 32 bytes of the Spore's type script args.
// Adjust this logic based on your actual Spore protocol.
let spore_type_args: Bytes = script.args().unpack();
if spore_type_args.len() < 32 { // Assuming Cluster ID is at least 32 bytes
continue; // Not enough args for Cluster ID, try next input
}
let cluster_id_from_spore_args = &spore_type_args[0..32];
if cluster_id_from_spore_args != target_cluster_id_bytes {
continue; // Cluster ID mismatch, try next input
}
// 3. Verify Spore ID
// Spore ID is typically the first 32 bytes of the Spore cell's data.
// Adjust this logic based on your actual Spore protocol.
let spore_data = load_cell_data(i, Source::Input)?;
if spore_data.len() < 32 { // Assuming Spore ID is at least 32 bytes
continue; // Not enough data for Spore ID, try next input
}
let spore_id_from_data = &spore_data[0..32];
if spore_id_from_data != target_spore_id_bytes {
continue; // Spore ID mismatch, try next input
}
// If all checks pass, we found the required Spore
spore_found = true;
break;
}
}
Ok(None) => {
// No type script for this input cell
}
Err(Error::IndexOutOfBound) => {
break; // No more input cells
}
Err(e) => return Err(e), // Other errors
}
}
if spore_found {
Ok(())
} else {
Err(Error::SporeCellNotFound) // Or a more specific error like ClusterIdMismatch or SporeIdMismatch
}
}
pub fn main() -> Result<(), Error> {
// Load the current script (DOBLock)
let script = load_script()?;
let args: Bytes = script.args().unpack();
// Deserialize args using Molecule
let dob_lock_args = DOBLockArgs::from_slice(&args).map_err(|_| Error::ArgsInvalid)?;
let target_spore_type_code_hash: [u8; 32] = dob_lock_args.target_spore_type_code_hash().into();
let target_spore_type_hash_type_byte: u8 = dob_lock_args.target_spore_type_hash_type().into();
let target_cluster_id_bytes: [u8; 32] = dob_lock_args.target_cluster_id().into();
let target_spore_id_bytes: [u8; 32] = dob_lock_args.target_spore_id().into();
let target_spore_type_hash_type = match target_spore_type_hash_type_byte {
0 => ScriptHashType::Data,
1 => ScriptHashType::Type,
2 => ScriptHashType::Data1,
4 => ScriptHashType::Data2, // CKB2023
_ => return Err(Error::InvalidHashType),
};
// The core logic: verify that a Spore cell matching the IDs is present in the inputs.
// This part of the logic assumes that the presence of the specific Spore cell in the inputs
// is sufficient to unlock the CKB. If other conditions are needed (e.g., witness arguments),
// they should be added here.
verify_spore_and_cluster(
&target_spore_type_code_hash,
target_spore_type_hash_type,
&target_cluster_id_bytes,
&target_spore_id_bytes,
)?;
Ok(())
}