博客 Demo
1. 什么是 Rooch
Rooch (opens in a new tab) 是一个快速、模块化、安全、开发人员友好的基础架构解决方案,用于构建 Web3 原生应用程序。
Rooch 于 2023 年 06 月 28 日,发布了第一个版本,版本名为 萌芽(Sprouting),版本号为 v0.1
。
2. 安装 Rooch
2.1 下载
在 Rooch 的 GitHub 发布页面 (opens in a new tab)可以找到预构建的二进制文件压缩包和相应版本的源码压缩包。如果想要体验 Git 版本,可以参考这个页面来编译安装 Rooch (opens in a new tab),下面引导你安装 Rooch 的 Release 版本。
wget https://github.com/rooch-network/rooch/releases/latest/download/rooch-ubuntu-latest.zip
注意:请选择对应自己系统的版本,我这里使用 Linux 的版本来演示。
2.2 解压
unzip rooch-ubuntu-latest.zip
解压文件存放在 rooch-artifacts
目录里,rooch
是我们预编译好的二进制程序。
rooch-artifacts
├── README.md
└── rooch
2.3 运行
进入解压文件夹 rooch-artifacts
并测试程序是否正常。
cd rooch-artifacts
./rooch
如果你能看到下面的输出内容,说明程序一切正常。
[joe@mx rooch]$ rooch
Usage: rooch <COMMAND>
Commands:
account Tool for interacting with accounts
init Tool for init with rooch
move CLI frontend for the Move compiler and VM
server Start Rooch network
state Get states by accessPath
object Get object by object id
resource Get account resource by tag
transaction Tool for interacting with transaction
event Tool for interacting with event
abi
env Interface for managing multiple environments
session-key Session key Commands
rpc
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
2.4 加入 PATH
为了方便后续使用,建议将 rooch
放入能被系统环境变量 PATH
检索的路径,或者将当前的解压目录通过 export
导出到 PATH
中。
- 方法一,复制
rooch
这个程序复制到/usr/local/bin
目录中(推荐)
sudo cp rooch /usr/local/bin
- 方法二,导出路径(不推荐)
使用下面这段小脚本将 rooch
添加到 Bash shell 的 PATH
。
echo "export PATH=\$PATH:$PWD" >> ~/.bashrc
source ~/.bashrc
3. 初始化 Rooch 配置
rooch init
运行初始化配置的命令后,会在用户的主目录($HOME
)创建一个 .rooch
目录,并生成 Rooch 账户的相关配置信息。
4. 创建新的 Rooch 项目
这部分将引导你如何在 Rooch 上创建一个博客的合约应用,并实现基本的**增查改删(CRUD)**功能。
4.1 创建 Move 项目
使用 rooch
集成的 move new
命令来创建一个名为 simple_blog
的博客应用。
rooch move new simple_blog
生成的 Move 项目里包含一个配置文件 Move.toml
和一个用于存放 Move 源代码的 sources
目录。
simple_blog
├── Move.toml
└── sources
我们可以简单看一下 Move.toml
文件包含了哪些内容。
[package]
name = "simple_blog"
version = "0.0.1"
[dependencies]
MoveStdlib = { git = "https://github.com/rooch-network/rooch.git", subdir = "frameworks/move-stdlib", rev = "main" }
MoveosStdlib = { git = "https://github.com/rooch-network/rooch.git", subdir = "frameworks/moveos-stdlib", rev = "main" }
RoochFramework = { git = "https://github.com/rooch-network/rooch.git", subdir = "frameworks/rooch-framework", rev = "main" }
[addresses]
simple_blog = "_"
std = "0x1"
moveos_std = "0x2"
rooch_framework = "0x3"
- 在 TOML 文件中包含三个表:
package
、dependencies
和addresses
,存放项目所需的一些元信息。 package
表用来存放项目的一些描述信息,这里包含两个键值对name
和version
来描述项目名和项目的版本号。dependencies
表用来存放项目所需依赖的元数据。addresses
表用来存放项目地址以及项目所依赖模块的地址,第一个地址是初始化 Rooch 配置时,生成在$HOME/.rooch/rooch_config/rooch.yaml
中的地址。
为了方便其他开发者部署,我们把 simple_blog
的地址用 _
替代,然后部署的时候通过 --named--addresses
来指定。
4.2 快速体验
这小节里,将引导你编写一个博客的初始化函数,并在 Rooch 中运行起来,体验编写 -> 编译 -> 发布 -> 调用
合约这样一个基本流程。
我们在 sources
目录里新建一个 simple_blog.move
文件,并开始编写我们的博客合约。
4.2.1 定义博客的结构
我们的博客系统允许每个人创建自己的博客,并保存自己的博客列表。首先,我们定义一个博客结构体:
struct MyBlog has key, store {
name: String,
articles: vector<ObjectID>,
}
这个结构体包含两个字段,一个是博客的名字,另一个是博客的文章列表。文章列表我们只保存文章 Object 的 ID。
然后定义一个创建博客的函数:
public fun create_blog(owner: &signer) {
let articles = vector::empty();
let myblog = MyBlog {
name: string::utf8(b"MyBlog"),
articles,
};
account::move_resource_to(owner, myblog);
}
public entry fun set_blog_name(owner: &signer, blog_name: String) {
assert!(std::string::length(&blog_name) <= 200, ErrorDataTooLong);
let owner_address = signer::address_of(owner);
// if blog not exist, create it
if (!account::exists_resource<MyBlog>(owner_address)) {
create_blog(owner);
};
let myblog = account::borrow_mut_resource<MyBlog>(owner_address);
myblog.name = blog_name;
}
创建博客就是初始化 MyBlog
数据结构,并把 MyBlog
保存在用户的存储空间内。同时提供了一个设置博客名称的入口函数,如果博客不存在,则先创建博客,然后设置博客名称。
然后再提供一个合约初始化函数,合约发布的时候会自动执行这个初始化函数,给发布合约的用户先自动初始化博客。
/// This init function is called when the module is published
/// The owner is the address of the account that publishes the module
fun init(owner: &signer) {
// auto create blog for module publisher
create_blog(owner);
}
然后,再提供一个查询博客列表的函数和添加删除文章的函数,全部代码如下:
module simple_blog::simple_blog {
use std::signer;
use std::string::{Self, String};
use std::vector;
use moveos_std::account;
use moveos_std::object::{Self, Object, ObjectID};
use simple_blog::simple_article::{Self, Article};
const ErrorDataTooLong: u64 = 1;
const ErrorNotFound: u64 = 2;
struct MyBlog has key, store {
name: String,
articles: vector<ObjectID>,
}
/// This init function is called when the module is published
/// The owner is the address of the account that publishes the module
fun init(owner: &signer) {
// auto create blog for module publisher
create_blog(owner);
}
public fun create_blog(owner: &signer) {
let articles = vector::empty();
let myblog = MyBlog {
name: string::utf8(b"MyBlog"),
articles,
};
account::move_resource_to(owner, myblog);
}
public entry fun set_blog_name(owner: &signer, blog_name: String) {
assert!(std::string::length(&blog_name) <= 200, ErrorDataTooLong);
let owner_address = signer::address_of(owner);
// if blog not exist, create it
if (!account::exists_resource<MyBlog>(owner_address)) {
create_blog(owner);
};
let myblog = account::borrow_mut_resource<MyBlog>(owner_address);
myblog.name = blog_name;
}
/// Get owner's blog's articles
public fun get_blog_articles(owner_address: address): &vector<ObjectID> {
let myblog = account::borrow_resource<MyBlog>(owner_address);
&myblog.articles
}
fun add_article_to_myblog(owner: &signer, article_id: ObjectID) {
let owner_address = signer::address_of(owner);
// if blog not exist, create it
if (!account::exists_resource<MyBlog>(owner_address)) {
create_blog(owner);
};
let myblog = account::borrow_mut_resource<MyBlog>(owner_address);
vector::push_back(&mut myblog.articles, article_id);
}
public entry fun create_article(
owner: signer,
title: String,
body: String,
) {
let article_id = simple_article::create_article(&owner, title, body);
add_article_to_myblog(&owner, article_id);
}
public entry fun update_article(
article_obj: &mut Object<Article>,
new_title: String,
new_body: String,
) {
simple_article::update_article(article_obj, new_title, new_body);
}
fun delete_article_from_myblog(owner: &signer, article_id: ObjectID) {
let owner_address = signer::address_of(owner);
let myblog = account::borrow_mut_resource<MyBlog>(owner_address);
let (contains, index) = vector::index_of(&myblog.articles, &article_id);
assert!(contains, ErrorNotFound);
vector::remove(&mut myblog.articles, index);
}
public entry fun delete_article(
owner: &signer,
article_id: ObjectID,
) {
delete_article_from_myblog(owner, article_id);
let article_obj = object::take_object(owner, article_id);
simple_article::delete_article(article_obj);
}
}
module simple_blog::simple_blog
用来声明我们的合约属于哪个模块,它的语法是module 地址::模块名
,花括号{}
里编写的就是合约的逻辑(功能)。use
语句导入我们编写合约时需要依赖的库。const
定义合约中使用的常量,通常用来定义一些错误代码。fun
是用来定义函数的关键字,通常在这里定义函数的功能。为了安全,这类函数禁止直接在命令行中调用,需要在入口函数中封装调用逻辑。entry fun
用来定义入口函数,entry
修饰符修饰的函数可以像脚本一样直接在命令行中调用。
4.2.2 编译 Move 合约
在发布合约前,需要编译我们的合约。这里通过 --named-addresses
来指定 simple_blog
模块的地址为当前设备上的 default
地址。
rooch move build --named-addresses simple_blog=default
编译结束后,如果没有错误,会在最后看到 Success
的提示信息。
INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY MoveosStdlib
INCLUDING DEPENDENCY RoochFramework
BUILDING simple_blog
Success
此时,项目文件夹会多出一个 build
目录,里面存放的就是 Move 编译器生成的合约字节码文件以及合约完整的源代码。
4.2.3 运行 Rooch 服务器
我们再打开另外一个终端,运行下面这条命令,Rooch 服务器会在本地启动 Rooch 容器服务,用于处理并响应合约的相关行为。
rooch server start
当启动 Rooch 服务后,会在最后看到这两条信息,表明 Rooch 的服务已经正常启动。
2023-07-03T15:44:33.315228Z INFO rooch_rpc_server: JSON-RPC HTTP Server start listening 0.0.0.0:6767
2023-07-03T15:44:33.315256Z INFO rooch_rpc_server: Available JSON-RPC methods : ["wallet_accounts", "eth_blockNumber", "eth_getBalance", "eth_gasPrice", "net_version", "eth_getTransactionCount", "eth_sendTransaction", "rooch_sendRawTransaction", "rooch_getAnnotatedStates", "eth_sendRawTransaction", "rooch_getTransactions", "rooch_executeRawTransaction", "rooch_getEventsByEventHandle", "rooch_getTransactionByHash", "rooch_executeViewFunction", "eth_getBlockByNumber", "rooch_getEvents", "eth_feeHistory", "eth_getTransactionByHash", "eth_getBlockByHash", "eth_getTransactionReceipt", "rooch_getTransactionInfosByOrder", "eth_estimateGas", "eth_chainId", "rooch_getTransactionInfosByHash", "wallet_sign", "rooch_getStates"]
提示:我们在前一个终端窗口操作合约相关的逻辑(功能)时,可以观察这个终端窗口的输出。
4.2.4 发布 Move 合约
rooch move publish --named-addresses simple_blog=default
当你看到类似这样的输出(status
为 executed
),就可以确认发布操作已经成功执行了:
{
//...
"execution_info": {
//...
"status": {
"type": "executed"
}
},
//...
}
在运行 Rooch 服务的终端也可以看到响应的处理信息:
2023-07-03T16:02:11.691028Z INFO connection{remote_addr=127.0.0.1:58770 conn_id=0}: jsonrpsee_server::server: Accepting new connection 1/100
2023-07-03T16:02:13.690733Z INFO rooch_proposer::actor::proposer: [ProposeBlock] block_number: 0, batch_size: 1
4.2.5 调用 Move 合约
此时,我们的博客合约已经发布到链上了,并且默认账户下已经初始化了博客。我们可以用状态查询命令来查看该账户下的博客 Resource:
rooch state --access-path /resource/{ACCOUNT_ADDRESS}/{RESOURCE_TYPE}
其中,{ACCOUNT_ADDRESS}
是账户地址,{RESOURCE_TYPE}
是资源类型,这里是 {MODULE_ADDRESS}::simple_blog::MyBlog
。这里 {ACCOUNT_ADDRESS}
和 {MODULE_ADDRESS}
都是我本机的默认账户地址。
我们可以查看 $HOME/.rooch/rooch_config/rooch.yaml
文件中的 active_address
这个键对应的值,即操作合约的默认账户地址。
我的地址为 0x5078ae74bac281e65fc446b467a843b186904a1b2d435f367030fc755eef1081
,后续将继续使用这个地址来演示相关操作。
所以我这里实际执行的命令应该是:
[joe@mx simple_blog]$ rooch state --access-path /resource/0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7/0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog
返回结果:
[
{
"value": "0x064d79426c6f6700",
"value_type": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog",
"decoded_value": {
"abilities": 12,
"type": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog",
"value": {
"articles": [],
"name": "MyBlog"
}
},
"display_fields": null
}
]
可以看到,MyBlog
Resource 已经存在,名称是默认的 MyBlog
,文章列表为空。
然后我们通过 set_blog_name
函数来设置博客名。调用合约入口函数的语法是:
rooch move run --function {ACCOUNT_ADDRESS}::{MODULE_NAME}::{FUNCTION_NAME} --sender-account {ACCOUNT_ADDRESS}
使用 rooch move run
命令运行一个函数。--function
指定函数名,需要传递一个完整的函数名,即发布合约的地址::模块名::函数名
,才能够准确识别需要调用的函数。--sender-account
指定调用这个函数的账户地址,即使用哪个账户来调用这个函数,任何人都可以调用链上的合约。
[joe@mx simple_blog]$ rooch move run --function 0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::set_blog_name --sender-account default --args 'string:Rooch blog'
这条命令执行时,会向链发送一笔交易,交易的内容就是就是调用博客合约中的 set_blog_name
函数。
执行成功后,再次运行前面的状态查询命令,查看博客 Resource:
[joe@mx simple_blog]$ rooch state --access-path /resource/0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7/0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog
[
{
"value": "0x0a526f6f636820626c6f6700",
"value_type": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog",
"decoded_value": {
"abilities": 12,
"type": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog",
"value": {
"articles": [],
"name": "Rooch blog"
}
},
"display_fields": null
}
]
可以看到,博客名已经修改成功。至此,我们体验了在 Rooch 中从零到一地安装,初始化配置,创建项目,编写合约,编译合约,发布合约,调用合约。
4.3 完善博客合约
接下来我们继续完善博客合约,增加博客文章的增查改删功能。
4.3.1 创建博客文章合约
我们在 sources
目录再创建一个 simple_article.move
文件,这个文件中存放文章的数据类型以及对文章进行 CRUD 操作的相关事件的定义。
定义文章数据类型,以及文章事件类型:
struct Article has key, store {
version: u64,
title: String,
body: String,
}
struct ArticleCreatedEvent has copy, store, drop {
id: ObjectID,
}
struct ArticleUpdatedEvent has copy, store, drop {
id: ObjectID,
version: u64,
}
struct ArticleDeletedEvent has copy, store, drop {
id: ObjectID,
version: u64,
}
文章数据结构包含三个字段,version
用来记录文章的版本号,title
用来记录文章标题,body
用来记录文章内容。
定义创建文章的函数:
/// Create article
public fun create_article(
owner: &signer,
title: String,
body: String,
): ObjectID {
assert!(std::string::length(&title) <= 200, ErrorDataTooLong);
assert!(std::string::length(&body) <= 2000, ErrorDataTooLong);
let article = Article {
version: 0,
title,
body,
};
let owner_addr = signer::address_of(owner);
let article_obj = object::new(article);
let id = object::id(&article_obj);
let article_created_event = ArticleCreatedEvent {
id,
};
event::emit(article_created_event);
object::transfer(article_obj, owner_addr);
id
}
这个函数中,先检查文章标题和内容的长度是否超过限制。然后创建文章对象,将文章对象添加到对象存储中,最后发送文章创建事件,返回文章的 ID。
然后定义修改函数:
/// Update article
public fun update_article(
article_obj: &mut Object<Article>,
new_title: String,
new_body: String,
) {
assert!(std::string::length(&new_title) <= 200, ErrorDataTooLong);
assert!(std::string::length(&new_body) <= 2000, ErrorDataTooLong);
let id = object::id(article_obj);
let article = object::borrow_mut(article_obj);
article.version = article.version + 1;
article.title = new_title;
article.body = new_body;
let article_update_event = ArticleUpdatedEvent {
id,
version: article.version,
};
event::emit(article_update_event);
}
这个函数中,先检查新的文章标题和内容的长度是否超过限制。然后从对象存储中获取文章对象,检查调用者是否是文章的所有者,如果不是,则抛出异常。最后更新文章对象的版本号,标题和内容,发送文章更新事件。
然后再定义删除函数:
/// Delete article
public fun delete_article(
article_obj: Object<Article>,
) {
let id = object::id(&article_obj);
let article = object::remove(article_obj);
let article_deleted_event = ArticleDeletedEvent {
id,
version: article.version,
};
event::emit(article_deleted_event);
drop_article(article);
}
这个函数中,先从对象存储中删除文章对象,检查调用者是否是文章的所有者,如果不是,则抛出异常。最后发送文章删除事件并销毁文章对象。
完整的合约代码如下:
module simple_blog::simple_article {
use std::signer;
use std::string::String;
use moveos_std::event;
use moveos_std::object::{Self, Object, ObjectID};
const ErrorDataTooLong: u64 = 1;
const ErrorNotOwnerAccount: u64 = 2;
//TODO should we allow Article to be transferred?
struct Article has key, store {
version: u64,
title: String,
body: String,
}
struct ArticleCreatedEvent has copy, store, drop {
id: ObjectID,
}
struct ArticleUpdatedEvent has copy, store, drop {
id: ObjectID,
version: u64,
}
struct ArticleDeletedEvent has copy, store, drop {
id: ObjectID,
version: u64,
}
/// Create article
public fun create_article(
owner: &signer,
title: String,
body: String,
): ObjectID {
assert!(std::string::length(&title) <= 200, ErrorDataTooLong);
assert!(std::string::length(&body) <= 2000, ErrorDataTooLong);
let article = Article {
version: 0,
title,
body,
};
let owner_addr = signer::address_of(owner);
let article_obj = object::new(article);
let id = object::id(&article_obj);
let article_created_event = ArticleCreatedEvent {
id,
};
event::emit(article_created_event);
object::transfer(article_obj, owner_addr);
id
}
/// Update article
public fun update_article(
article_obj: &mut Object<Article>,
new_title: String,
new_body: String,
) {
assert!(std::string::length(&new_title) <= 200, ErrorDataTooLong);
assert!(std::string::length(&new_body) <= 2000, ErrorDataTooLong);
let id = object::id(article_obj);
let article = object::borrow_mut(article_obj);
article.version = article.version + 1;
article.title = new_title;
article.body = new_body;
let article_update_event = ArticleUpdatedEvent {
id,
version: article.version,
};
event::emit(article_update_event);
}
/// Delete article
public fun delete_article(
article_obj: Object<Article>,
) {
let id = object::id(&article_obj);
let article = object::remove(article_obj);
let article_deleted_event = ArticleDeletedEvent {
id,
version: article.version,
};
event::emit(article_deleted_event);
drop_article(article);
}
fun drop_article(article: Article) {
let Article {
version: _version,
title: _title,
body: _body,
} = article;
}
/// Read function of article
/// get article version
public fun version(article: &Article): u64 {
article.version
}
/// get article title
public fun title(article: &Article): String {
article.title
}
/// get article body
public fun body(article: &Article): String {
article.body
}
}
最新源码请参考:examples/simple_blog/sources/simple_blog.move (opens in a new tab)
4.3.2 博客合约集成文章合约
接下来,我们在 simple_blog.move
中集成文章合约,并提供入口函数:
public entry fun create_article(
owner: signer,
title: String,
body: String,
) {
let article_id = simple_article::create_article(&owner, title, body);
add_article_to_myblog(&owner, article_id);
}
public entry fun update_article(
article_obj: &mut Object<Article>,
new_title: String,
new_body: String,
) {
simple_article::update_article(article_obj, new_title, new_body);
}
public entry fun delete_article(
owner: &signer,
article_id: ObjectID,
) {
delete_article_from_myblog(owner, article_id);
let article_obj = object::take_object(owner, article_id);
simple_article::delete_article(article_obj);
}
创建和删除文章的时候,同时更新博客中的文章列表。
4.3.3 创建博客文章
可以像下面这样,使用 Rooch CLI 提交一个交易,创建一篇测试文章:
rooch move run --function default::simple_blog::create_article --sender-account default --args 'string:Hello Rooch' --args "string:Unlocking infinite utility for the Bitcoin Economy"
--function
指定执行发布在 0x5078ae74bac281e65fc446b467a843b186904a1b2d435f367030fc755eef1081
地址上的 simple_blog
模块中的 create_article
函数,即新建一篇博客文章。--sender-account
指定谁来提交这个交易。这个函数要求我们必须给它传递两个参数,通过 --args
来指定,第一个是文章的标题,我取名为 Hello Rooch
;第二个是文章的内容,我写上 Rooch 的标语(slogan):Unlocking infinite utility for the Bitcoin Economy
。
参数传递的是字符串,需要使用引号将内容包裹起来,并且通过 string:
来显示指定,在第二个参数的内容中有单引号,所以使用双引号包裹,否则必须使用转义符(\
)。
你可以随意更换 --args
后面的第一个参数(title
)和第二个参数(body
)的内容,多创建几篇文章。
4.3.4 查询文章
现在,你可以通过查询事件,得到已创建好的文章的 ObjectID
:
curl --location --request POST 'http://localhost:6767' \
--header 'Content-Type: application/json' \
--data-raw '{
"id":101,
"jsonrpc":"2.0",
"method":"rooch_getEventsByEventHandle",
"params":["0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_article::ArticleCreatedEvent", null, "1000", false, {"decode":true}]
}'
由于输出的内容比较多,可以在上面的命令最尾添加一个管道操作( | jq '.result.data[0].decoded_event_data.value.id'
),来快速筛选出第一篇文章的 ObjectID
。
提示:在使用
jp
命令(jq - commandline JSON processor)之前,你可能需要先安装它。
添加 jp
处理后的命令像下面这样:
curl --location --request POST 'http://localhost:6767' --header 'Content-Type: application/json' --data-raw '{
"id":101,
"jsonrpc":"2.0",
"method":"rooch_getEventsByEventHandle",
"params":["0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_article::ArticleCreatedEvent", null, "1000", false, {"decode":true}]
}' | jq '.result.data[0].decoded_event_data.value.id'
通过 jp
来筛选出的博客的对象 ID 为:
"0xf3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379"
然后,你可以使用 Rooch CLI 来查询对象的状态,通过 --id
来指定文章对象的 ID(注意替换为你的文章的 ObjectID):
rooch state --access-path /object/0x6067b5c1f0a6a9d059ab0e2e4fe5ce12832cc4036aa5ca451611d0dd971192e1
[joe@mx simple_blog]$ rooch state --access-path /object/0xf3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379
[
{
"value": "0x01f3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379ff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7005350415253455f4d45524b4c455f504c414345484f4c4445525f484153480000000000000000000000000000000000000b48656c6c6f20526f6f636833416363656c65726174696e6720576f726c642773205472616e736974696f6e20746f20446563656e7472616c697a6174696f6e",
"value_type": "0x2::object::ObjectEntity<0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_article::Article>",
"decoded_value": {
"abilities": 0,
"type": "0x2::object::ObjectEntity<0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_article::Article>",
"value": {
"flag": 0,
"id": "0xf3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379",
"owner": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7",
"size": "0",
"state_root": "0x5350415253455f4d45524b4c455f504c414345484f4c4445525f484153480000",
"value": {
"abilities": 12,
"type": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_article::Article",
"value": {
"body": "Unlocking infinite utility for the Bitcoin Economy",
"title": "Hello Rooch",
"version": "0"
}
}
}
},
"display_fields": null
}
]
注意观察输出中,title
和 body
这两个键值对,能看到这个对象确实“存储着”刚刚写的那篇博客文章。
我们也可以用前面的命令来查询账户下的 MyBlog
Resource:
[joe@mx simple_blog]$ rooch state --access-path /resource/0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7/0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog
[
{
"value": "0x0a526f6f636820626c6f670101f3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379",
"value_type": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog",
"decoded_value": {
"abilities": 12,
"type": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog",
"value": {
"articles": ["0xf3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379"],
"name": "Rooch blog"
}
},
"display_fields": null
}
]
可以看到,MyBlog
中的 articles
字段,存储着我们刚刚创建的那篇文章的对象 ID。
4.3.5 更新文章
我们尝试使用 update_article
函数来更新文章的内容。
--function
指定执行发布在 0x5078ae74bac281e65fc446b467a843b186904a1b2d435f367030fc755eef1081
地址上的 simple_blog
模块中的 update
函数,即更新一篇博客文章。同样也需要使用 --sender-account
来指定发送这个更新文章交易的账户。这个函数要求我们必须给它传递三个参数,通过 --args
来指定,第一个参数是要修改文章的对象 ID,后面的两个参数分别对应文章的标题和正文。
将文章的标题修改为 Foo
,文章正文修改为 Bar
:
rooch move run --function default::simple_blog::update_article --sender-account default --args 'object_id:0xf3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379' --args 'string:Foo' --args 'string:Bar'
除了使用 Rooch CLI,你还可以通过调用 JSON RPC 来查询对象的状态:
curl --location --request POST 'http://127.0.0.1:6767/' --header 'Content-Type: application/json' --data-raw '{
"id":101,
"jsonrpc":"2.0",
"method":"rooch_getStates",
"params":["/object/0xf3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379", {"decode": true}]
}'
在输出中,可以观察到文章的标题和正文已成功修改:
{
"jsonrpc": "2.0",
"result": [
{
"value": "0x01f3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379ff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7005350415253455f4d45524b4c455f504c414345484f4c4445525f4841534800000000000000000000010000000000000003466f6f03426172",
"value_type": "0x2::object::ObjectEntity<0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_article::Article>",
"decoded_value": {
"abilities": 0,
"type": "0x2::object::ObjectEntity<0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_article::Article>",
"value": {
"flag": 0,
"id": "0xf3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379",
"owner": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7",
"size": "0",
"state_root": "0x5350415253455f4d45524b4c455f504c414345484f4c4445525f484153480000",
"value": {
"abilities": 12,
"type": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_article::Article",
"value": { "body": "Bar", "title": "Foo", "version": "1" }
}
}
},
"display_fields": null
}
],
"id": 101
}
4.3.6 删除文章
可以这样提交一个交易,调用 simple_blog::delete_article
删除文章:
rooch move run --function default::simple_blog::delete_article --sender-account default --args 'object_id:0xf3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379'
--function
指定执行发布在 0x5078ae74bac281e65fc446b467a843b186904a1b2d435f367030fc755eef1081
地址上的 simple_blog
模块中的 delete_article
函数,即删除一篇博客文章。同样也需要使用 --sender-account
来指定发送这个删除文章交易的账户。这个函数只需给它传递一个参数,即文章对应的对象 ID,通过 --args
来指定。
4.3.7 检查文章是否正常删除
[joe@mx simple_blog]$ rooch state --access-path /object/0xf3264835925cf7573b0da6a5e8b97d6051b695455b012c1d87482b6652892379
[
null
]
可以看到,查询文章的对象 ID 时,结果反回 null
。说明文章已经被删除了。再查询 MyBlog
Resource:
[joe@mx simple_blog]$ rooch state --access-path /resource/0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7/0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog
[
{
"value": "0x0a526f6f636820626c6f6700",
"value_type": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog",
"decoded_value": {
"abilities": 12,
"type": "0xff795489ecdf184de120244354a3bcf7471cd7683780cb6f19b2336b2ca239b7::simple_blog::MyBlog",
"value": {
"articles": [],
"name": "Rooch blog"
}
},
"display_fields": null
}
]
可以看到,articles
数组为空,说明文章列表也已经更新。
5. 总结
到这里,相信你已经对 Rooch v0.1 如何运行,如何发布合约,以及如何跟合约交互有了基本的了解。想要在 Rooch 上体验更多的合约例子,请参见 rooch/examples
(opens in a new tab)。
如果想直接体验这个博客合约的功能,可以直接下载博客源码 (opens in a new tab),解压,并切换到博客合约项目的根目录,交互方式请参照上文。
wget https://github.com/rooch-network/rooch/archive/refs/heads/main.zip
unzip main.zip
cd rooch-main/examples/simple_blog