上一篇blog我们介绍了如何在Substrate区块链中储存和读取u32类型的数和map类型的映射。接下来我们尝试如何在Substrate中定义结构体,并读取和储存结构体。
自定义结构体
在Substrate中自定义结构体可以用以下代码:
#[derive(Encode, Decode, Default, Clone, PartialEq)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct MyStruct<A, B> {
some_number: u32,
some_generic: A,
some_other_generic: B,
}
注意,这里的derive中出现了Encode和Decode traits (traits可以看作在rust语言中类似于Java的Interface), 所以我们需要将parity_codec
导入到文件中:
use parity_codec::{Encode, Decode};
在以上定义结构体的代码中,我们使用了Generic(泛型),泛型可以让我们抽象出类似的操作而不用考虑具体类型。这里,A
和B
即为在结构体中需要使用的泛型。如果我们想在some_generic
中使用Moment
类型,在 some_other_generic
中使用Hash
类型,那么哦我们可以用以下定义来实现:
decl_storage! {
trait Store for Module<T: Trait> as Example {
MyItem: map T::AccountId => MyStruct<T::Moment, T::Hash>;
}
}
我们还注意到在定义结构体的前面有#[derive(...)]
语句。这是Rust编译器提供的特性,它可以引入一些基础traits的实现。同时,#[cfg_attr(feature = "std", derive(Debug))]
是对于Debug trait的基础实现。详情可以阅读derive
在Module函数中使用结构体
我们已经在decl_storage!
中声明了一个map结构,key为AccountId,value是自定义的MyStruct,下面就可以为其进行赋值和更新。
以下代码为创建一个结构体并赋值的一个模块函数:
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
fn create_struct(origin, value: u32, moment: T::Moment, hash: T::Hash) -> Result {
let sender = ensure_signed(origin)?;
let new_struct = MyStruct {
some_number: value,
some_generic: moment,
some_other_generic: hash,
};
<MyItem<T>>::insert(sender, new_struct);
Ok(())
}
}
}
可以看到,在这个函数create_struct
中,我们创建一个new_struct
的结构体实例,并将其中的some_number
,some_generic
,和some_other_generic
进行了赋值。然后使用<MyItem<T>>::insert(sender, new_struct);
为mapMyItem
增加新的键值对:sender
, new_struct
。
例子
承接上一个blog https://www.jianshu.com/p/42657bc89df4 的例子,下面我们依照上述方法,对每个(account, subject)
增加一个Credential
结构体。
一个Credential
中有以下属性:
-
subject
:u32
-
when
:Timestamp
-
by
:AccountId
在Module<T: Trait>
中增加一个issue_credential()
函数,在此函数中使用Credential
类型创建new_cred
,并将其键入到runtime存储中。
use support::{decl_storage, decl_module, StorageValue, StorageMap, dispatch::Result};
use system::ensure_signed;
use runtime_primitives::traits::{As, Hash};
use parity_codec::{Encode, Decode};
pub trait Trait: system::Trait + timestamp::Trait {}
#[derive(Encode, Decode, Default, Clone, PartialEq)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Credential<Timestamp, AccountId> {
subject: u32,
when: Timestamp,
by: AccountId
}
decl_storage! {
trait Store for Module<T: Trait> as VerifiableCreds {
SubjectCount: u32;
Subjects: map u32 => T::AccountId;
Credentials get(credentials): map (T::AccountId, u32) => Credential<T::Moment, T::AccountId>;
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
fn create_subject(origin) -> Result {
let sender = ensure_signed(origin)?;
let subject = <SubjectCount<T>>::get();
<SubjectCount<T>>::put(subject + 1);
<Subjects<T>>::insert(subject, sender);
Ok(())
}
// NOTE: We added a new function
fn issue_credential(origin, to: T::AccountId, subject: u32) -> Result {
let sender = ensure_signed(origin)?;
let new_cred = Credential {
subject: subject,
when: <timestamp::Module<T>>::get(),
by: sender,
};
<Credentials<T>>::insert((to, subject), new_cred);
Ok(())
}
}
}
在上述代码例子中,可以看到Credential
结构体被声明为pub类型,在decl_storage!
macro中添加了Credentials get(credentials): map (T::AccountId, u32) => Credential<T::Moment, T::AccountId>;
map映射。在Module<T: Trait>
中,添加了新的函数issue_credential
:
fn issue_credential(origin, to: T::AccountId, subject: u32) -> Result {
let sender = ensure_signed(origin)?;
let new_cred = Credential {
subject: subject,
when: <timestamp::Module<T>>::get(),
by: sender,
};
<Credentials<T>>::insert((to, subject), new_cred);
Ok(())
}
其中,我们使用<timestamp::Module<T>>::get()
来获取当前的timestamp。最后将new_cred
添加到map中。
与区块链交互并查看储存的结构体
我们已经将新的结构体与其对应的交互函数添加到了Substrate runtime当中,下面我们需要与区块链进行交互并查看程序逻辑是否正确。
在启动节点前,我们仍然需要首先编译,并清除之前的区块链数据:
./scripts/build.sh
cargo build --release
./target/release/substrate-verifiable-credentials purge-chain --dev
./target/release/substrate-verifiable-credentials --dev
因为我们在区块链中引入了新的结构体,Polkadots-JS UI可以根据用户自定义JSON文件来反序列化结构体数据。
在Polkadots中登记定制的结构体
Polkadots UI提供简单的方式来引入新的结构体,这样页面就可以反序列化结构体数据以便呈现给终端用户。
进入Setting
, 选择Developer tab,将以下JSON文件提交:
{
"Credential": {
"subject": "u32",
"when": "Moment",
"by": "AccountId"
}
}
image.png
然后,我们可以调用issueCredentials
函数。进入Extrinsic,选择VerifiableCredts>issueCredentials(AccountId, u32)
。这里会要求用户输入给subject发放Credential对应的AccountId,和与之对应的subject id。
最后,我们需要在Chain State中查看我们之前存储的credential对象。首先进入Chain State,然后选择VerifiableCreds > credentials((AccountId, u32)): Credential
,之后可以选择对应持有Credential的账户:Bob,和对应subject:0。点击蓝色+号,即可看到我们存储的Credential的值。可以看到,发放给Bob的Credential对应的对象为0,时间是1573982250,发放者为ALICE的地址。
网友评论