Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
S
share
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
黄刚
share
Commits
3e7bd2a1
Commit
3e7bd2a1
authored
Jan 11, 2019
by
Hugo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update
parent
0c84ad7b
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
273 additions
and
141 deletions
+273
-141
js合约.md
js合约.md
+273
-141
No files found.
js合约.md
View file @
3e7bd2a1
## 1 jsvm 接口
[
TOC
]
## 1 jsvm 接口
[
TOC
]
[
TOC
]
## 1 JavaScript 语言DApp开发
### 1.1 部署一个jsvm合约 CreateTransaction
*
使用JavaScript语言进行DApp开发,首先要按照一定格式编写一个合约,然后可以使用cli调试工具或者调用JSONRPC接口将合约部署到Chain33上,通过调用相应的方法完成想要的操作。
### 1.1 用JavaScript写一个自己的合约
>一般js文件包含如下规则:
>定义Init(context)函数:该函数在部署合约时会被调用,作用是为一个合约准备资源,主要是存储对象kvcreator和上下文信息context。
>定义Exec对象的xxxx方法:将一些数据写入stateDB,并传递一些需要记录在表中的log信息。
>定义ExecLocal对象的xxxx:将log信息写入表或写入一些数据到localDB。
>定义Query对象的xxxx方法:查询表中的数据或者localDB中的数据。
>基于chain33的框架,通过jsvm的Call调用一个方法会依次执行对应的Exec对象和ExecLocal对象的同名方法,比如Call的payload.funcname为"NewGame",就会依次执行Exec对象的NewGame方法和ExecLocal对象的NewGame方法。
下面结合
[
game.js
](
https://github.com/33cn/plugin/blob/master/plugin/dapp/js/executor/game.js
)
具体介绍一下
>game.js定义了一种猜数字的游戏,游戏规则:
>庄家出一个 0 - 10 的数字 hash(随机数 + 9) (一共的赔偿金额)。 对应NewGame方法。
>用户可以猜这个数字,多个用户都可以猜测。对应Guess方法
>开奖。对应CloseGame方法
>下面仅以NewGame方法举例,另外两个方法可以参考上面链接的代码。
#### 1.1.1 定义存储结构
为了存储上述信息,需要创建三张表,一张GameLocalTable,一张MatchLocalTable,一张MatchGameTable,其中MatchGameTable是基于上述两表的连接表,类似于关系数据库的join操作产生的临时表。
创建一张表包含config和defaultvalue两部分(创建连接表只需调用JoinTable方法即可)config设置表结构,包含表属性配置项(用#开头)和列属性配置两类,表属性有表名,主键和存储db类型三个。defaultvalue用于设置列属性的默认值。
下面是创建GameLocalTable的代码:
```
js
function
GameLocalTable
(
kvc
)
{
this
.
config
=
{
"#tablename"
:
"game"
,
//定义表名
"#primary"
:
"gameid"
,
//定义主键
"#db"
:
"localdb"
,
//定义存储的db类型,一般为localdb
"gameid"
:
"%018d"
,
//表主键,游戏id,可以用交易总索引(txID())定义
"status"
:
"%d"
,
//游戏状态:0-未开始,1-开始,2-关闭
"hash"
:
"%s"
,
//交易hash
"addr"
:
"%s"
,
//创建交易者的地址
}
this
.
defaultvalue
=
{
"gameid"
:
0
,
"status"
:
0
,
"hash"
:
""
,
"addr"
:
""
,
}
return
new
Table
(
kvc
,
this
.
config
,
this
.
defaultvalue
)
}
```
下面是创建MatchLocalTable的代码:
```
js
function
MatchLocalTable
(
kvc
)
{
this
.
config
=
{
"#tablename"
:
"match"
,
//定义表名
"#primary"
:
"id"
,
//定义主键
"#db"
:
"localdb"
,
//定义存储的db类型,一般为localdb
"id"
:
"%018d"
,
//表主键,可以用交易总索引(txID())定义
"gameid"
:
"%018d"
,
//表外键,对应GameLocalTable表主键
"hash"
:
"%s"
,
//交易hash
"addr"
:
"%s"
,
//参与游戏的猜数字者的地址
}
this
.
defaultvalue
=
{
"id"
:
0
,
"gameid"
:
0
,
"hash"
:
""
,
"addr"
:
""
,
}
return
new
Table
(
kvc
,
this
.
config
,
this
.
defaultvalue
)
}
```
下面是创建MatchGameTable的代码:
```
js
function
MatchGameTable
(
kvc
)
{
//参数lefttable是使用外键的表,数据量相对较大,参数index是需要查询连接表的字段名
return
new
JoinTable
(
MatchLocalTable
(
kvc
),
GameLocalTable
(
kvc
),
"addr#status"
)
}
```
#### 1.1.2 Init函数
```
js
function
Init
(
context
)
{
this
.
kvc
=
new
kvcreator
(
"init"
)
//创建init类型对象,用于存储key,value值
this
.
context
=
context
//保存上下文信息
return
this
.
kvc
.
receipt
()
}
```
#### 1.1.3 Exec的xxxx方法
此方法可以通过chain33-cli调试工具或JSONRPC的call方法调用到,xxxx与payload.funcname值一致。主要作用是通过kvc.add()将一些信息写入stateDB,通过kvc.addlog()传递log信息。
```
js
Exec
.
prototype
.
NewGame
=
function
(
args
)
{
//
var
game
=
{
__type__
:
"game"
}
game
.
gameid
=
this
.
txID
()
game
.
height
=
this
.
context
.
height
game
.
randhash
=
args
.
randhash
game
.
bet
=
args
.
bet
game
.
hash
=
this
.
context
.
txhash
game
.
obet
=
game
.
bet
game
.
addr
=
this
.
context
.
from
game
.
status
=
1
//open
//最大值是 9000万,否则js到 int 会溢出
if
(
game
.
bet
<
10
*
COINS
||
game
.
bet
>
10000000
*
COINS
)
{
throwerr
(
"bet low than 10 or hight than 10000000"
)
}
if
(
this
.
kvc
.
get
(
game
.
randhash
))
{
//如果randhash 已经被使用了
throwerr
(
"dup rand hash"
)
}
var
err
=
this
.
acc
.
execFrozen
(
this
.
name
,
this
.
context
.
from
,
game
.
bet
)
//冻结庄家的奖池
throwerr
(
err
)
this
.
kvc
.
add
(
game
.
gameid
,
game
)
//存储key:gameid与value:game到stateDB
this
.
kvc
.
add
(
game
.
randhash
,
"ok"
)
//存储key:randhash与value:"ok"到stateDB
this
.
kvc
.
addlog
(
game
)
//传递game对象到ExecLocal.prototype.NewGame
return
this
.
kvc
.
receipt
()
}
```
#### 1.1.4 ExecLocal的xxxx方法
此方法将对应的Exec的xxxx方法传递的log信息写入到对应的表中,或者调用kvc.add()直接将数据写入stateDB。
```
js
function
localprocess
(
args
)
{
var
local
=
MatchGameTable
(
this
.
kvc
)
local
.
addlogs
(
this
.
logs
)
//操作join表,达到自动更新关联表的目的
local
.
save
()
//保存数据
return
this
.
kvc
.
receipt
()
}
ExecLocal
.
prototype
.
NewGame
=
function
(
args
)
{
return
localprocess
.
call
(
this
,
args
)
}
```
#### 1.1.5 Query的xxxx方法
此方法可以通过chain33-cli调试工具或JSONRPC的query方法调用到,xxxx与payload.funcname值一致,实现按照传入参数查询数据的功能,可以通过表进行查询,也可以直接查询localDB。
```
js
//提供按照游戏创建者地址查询game信息
Query
.
prototype
.
ListGameByAddr
=
function
(
args
)
{
var
local
=
GameLocalTable
(
this
.
kvc
)
var
q
=
local
.
query
(
"addr"
,
args
.
addr
,
args
.
primaryKey
,
args
.
count
,
args
.
direction
)
return
querytojson
(
q
)
}
```
### 1.2 使用cli调试工具部署智能合约
chain33-cli是我们的调试程序,使用chain33-cli命令可以方便的完成一些操作,下面演示使用chain33-cli命令部署智能合约到chain33链上以及调用智能合约中的方法。
---
> **step1:**使用chain33-cli命令将智能合约部署到chain33链上:
./chain33-cli send jsvm create -c "合约代码文件的路径" -n "合约名" -k "合约创建者的私钥或者地址"
root@ubuntu055-3:/home/lcj0# ./chain33-cli send jsvm create -c "/home/hugo/game.js" -n game -k 14KEKbYtKKQm4wMthSK9J4La4nAiidGozt
---
>
**step2:**上面的合约代码部署成功后,会生成一个交易哈希,然后使用此哈希查询交易:
./chain33-cli tx query_hash -s "交易哈希"
root@ubuntu055-3:/home/lcj0# ./chain33-cli tx query_hash -s 0xd4cfd1606ea1d9382b3334351e3e741cad8a68b4e7b38a4b02c981203865cb03
*
在返回的结果可以看到如下:其中name 合约名称,涉及到合约转账或取回操作时要使用此名称;code是js文件内容;
```
json
...
"
execer"
:
"
jsvm"
,
"
payload"
:
{
"
create"
:
{
"
code"
:
"
...js文件内容..."
,
"
name"
:
"
game"
},
"
ty"
:
0
}
,
...
```
---
> **step3:**向合约充值
由于game合约需要创建游戏者(庄家)首先抵押一些资产做奖池,所以需要先充值到合约中,通过命令行充100 BTY到合约中,注意-e 参数必须是"user.jsvm.合约名"的形式。
./chain33-cli send coins send_exec -a BTY个数 -e "合约名" -n "附言" -k "合约创建者的私钥或者地址"
./chain33-cli send coins send_exec -a 100 -e "user.jsvm.game" -n "12312" -k 14KEKbYtKKQm4wMthSK9J4La4nAiidGozt
>使用查询交易命令查询充值是否成功。
./chain33-cli tx query_hash -s 0xa627e07e3ba7c47fd2e16812b34f82d6e07c6c074d2043abdb6b2d57dc9b4d4b
```json
...
"ty": 8,
"tyName": "LogExecDeposit",
"log": {
"execAddr": "15mjHYNvPkSMrp3SQxtczXL6cinMAEdeKs",
"prev": {
"currency": 0,
"balance": "0",
"frozen": "0",
"addr": "14KEKbYtKKQm4wMthSK9J4La4nAiidGozt"
},
"current": {
"currency": 0,
"balance": "10000000000",
"frozen": "0",
"addr": "14KEKbYtKKQm4wMthSK9J4La4nAiidGozt"
}
}
...
```
可以看到current.balance为100
*
1e8
---
> **step4:**充值完成后,调用合约中的NewGame方法创建游戏。
./chain33-cli send jsvm call -n "合约名" -f "方法名" -a "以json格式表示的参数列表" -k "合约创建者的私钥或者地址"
./chain33-cli send jsvm call -n game -f NewGame -a "{\"bet\":1500000000, \"randhash\":\"0x5d2ac15654aa1859af80d1dfa5d7a5775c359fe3a49abce03796f4fbc313b57f\"}" -k 14KEKbYtKKQm4wMthSK9J4La4nAiidGozt
>使用查询交易命令查询是否创建成功。
```
json
...
"ty"
:
9
,
"tyName"
:
"LogExecFrozen"
,
"log"
:
{
"execAddr"
:
"15mjHYNvPkSMrp3SQxtczXL6cinMAEdeKs"
,
"prev"
:
{
"currency"
:
0
,
"balance"
:
"10000000000"
,
"frozen"
:
"0"
,
"addr"
:
"14KEKbYtKKQm4wMthSK9J4La4nAiidGozt"
},
"current"
:
{
"currency"
:
0
,
"balance"
:
"8500000000"
,
"frozen"
:
"1500000000"
,
"addr"
:
"14KEKbYtKKQm4wMthSK9J4La4nAiidGozt"
}
},
...
```
可以看到冻结量:current.frozen为15
* 1e8,剩余量:current.balance为85*
1e8
```
json
...
"ty"
:
10000
,
"tyName"
:
"TyLogJs"
,
"log"
:
{
"data"
:
"{
\"
__type__
\"
:
\"
game
\"
,
\"
addr
\"
:
\"
14KEKbYtKKQm4wMthSK9J4La4nAiidGozt
\"
,
\"
bet
\"
:1500000000,
\"
gameid
\"
:4100000,
\"
hash
\"
:
\"
0x2b3e9d1bd4dfcd6766f6aacc366d8e360e9896de1cc4da0501b30d696a506d2a
\"
,
\"
height
\"
:41,
\"
obet
\"
:1500000000,
\"
randhash
\"
:
\"
0x5d2ac15654aa1859af80d1dfa5d7a5775c359fe3a49abce03796f4fbc313b57f
\"
,
\"
status
\"
:1}"
},
...
```
可以看到游戏创建成功了,gameid为4100000。然后可以使用同样的方法,调用合约中的Guess方法和CloseGame方法来猜数字和开奖,不在此赘述。
---
> **step5:** 调用查询接口列出由某个地址创建的游戏
./chain33-cli jsvm query -a "以json格式表示的参数列表" -f "方法名" -n "合约名"
./chain33-cli jsvm query -a "{\"addr\":\"14KEKbYtKKQm4wMthSK9J4La4nAiidGozt\", \"count\":20}" -f "ListGameByAddr" -n game
返回内容如下:
```
json
{
"data"
:
"[{
\"
left
\"
:{
\"
__type__
\"
:
\"
game
\"
,
\"
addr
\"
:
\"
14KEKbYtKKQm4wMthSK9J4La4nAiidGozt
\"
,
\"
bet
\"
:1500000000,
\"
gameid
\"
:4100000,
\"
hash
\"
:
\"
0x2b3e9d1bd4dfcd6766f6aacc366d8e360e9896de1cc4da0501b30d696a506d2a
\"
,
\"
height
\"
:41,
\"
obet
\"
:1500000000,
\"
randhash
\"
:
\"
0x5d2ac15654aa1859af80d1dfa5d7a5775c359fe3a49abce03796f4fbc313b57f
\"
,
\"
status
\"
:1}}]"
}
```
>可以使用相同的方法调用ListMatchByAddr方法,列举出某地址创建的处于某种状态的游戏,由于用到连表查询,需要先通过方法JoinKey查到结果,再将结果作为参数,调用ListMatchByAddr方法获得最终结果。
### 1.3 调用JSONRPC接口部署智能合约
#### 1.3.1 部署一个jsvm合约 CreateTransaction
> 这里创建的是原始交易,除此之外,还需要进行交易的签名以及发送操作
> 这里创建的是原始交易,除此之外,还需要进行交易的签名以及发送操作
> 如果合约中要使用数字货币,在调用合约前需要先向合约充值
> 如果合约中要使用数字货币,在调用合约前需要先向合约充值
...
@@ -67,7 +335,7 @@ Request1 部署合约交易:
...
@@ -67,7 +335,7 @@ Request1 部署合约交易:
}
}
```
```
###
1
.2 调用创建的jsvm合约的方法 CreateTransaction
###
# 1.3
.2 调用创建的jsvm合约的方法 CreateTransaction
**请求报文:**
**请求报文:**
```
json
```
json
...
@@ -145,7 +413,7 @@ Response:
...
@@ -145,7 +413,7 @@ Response:
}
}
```
```
###
1
.3 查询创建的jsvm合约
###
# 1.3
.3 查询创建的jsvm合约
**请求报文:**
**请求报文:**
...
@@ -225,139 +493,4 @@ Response:
...
@@ -225,139 +493,4 @@ Response:
},
},
"error"
:
null
"error"
:
null
}
}
```
### 1.4 如何用JavaScript写一个自己的合约
>一般js文件包含如下规则:
>定义Init(context)函数:该函数在部署合约时会被调用,作用是为一个合约准备资源,主要是存储对象kvcreator和上下文信息。
>定义Exec对象的xxxx方法:将一些数据写入stateDB,并传递一些需要记录在表中的log信息
>定义ExecLocal对象的xxxx:将log信息写入表或写入一些数据到localDB
>定义Query对象的xxxx方法:查询表中的数据或者
>基于chain33的框架,通过上面1.2的Call调用将会依次执行对应的Exec.的xxxx方法和ExecLocal的xxxx方法,比如Call的payload.funcname为"NewGame",就会依次执行Exec的NewGame方法和ExecLocal的NewGame方法。
下面结合game.js具体介绍一下
>game.js定义了一种猜数字的游戏,游戏规则:
>庄家出一个 0 - 10 的数字 hash(随机数 + 9) (一共的赔偿金额)。 对应NewGame方法。
>用户可以猜这个数字,多个用户都可以猜测。对应Guess方法
>开奖。对应CloseGame方法
>注意:像上述三个的方法,Exec对象和ExecLocal对象要分别实现的一个同名的方法。
#### 1.4.1 定义存储结构
为了存储上述信息,需要创建三张表,一张GameLocalTable,一张MatchLocalTable,一张MatchGameTable,其中MatchGameTable是基于上述两表的连接表类似于关系数据库的join操作产生的临时表。
创建一张表包含config和defaultvalue两部分,config设置表结构,包含表属性配置项(用#开头)和列属性配置两类,表属性有表名,主键和存储db类型三个。defaultvalue用于设置列属性的默认值。
下面是创建GameLocalTable的代码
```
js
function
GameLocalTable
(
kvc
)
{
this
.
config
=
{
"#tablename"
:
"game"
,
//定义表名
"#primary"
:
"gameid"
,
//定义主键
"#db"
:
"localdb"
,
//定义存储的db类型,一般为localdb
"gameid"
:
"%018d"
,
//表主键,游戏id,可以用交易总索引(txID())定义
"status"
:
"%d"
,
//游戏状态:0-未开始,1-开始,2-关闭
"hash"
:
"%s"
,
//交易hash
"addr"
:
"%s"
,
//创建交易者的地址
}
this
.
defaultvalue
=
{
"gameid"
:
0
,
"status"
:
0
,
"hash"
:
""
,
"addr"
:
""
,
}
return
new
Table
(
kvc
,
this
.
config
,
this
.
defaultvalue
)
}
```
下面是创建MatchLocalTable的代码
```
js
function
MatchLocalTable
(
kvc
)
{
this
.
config
=
{
"#tablename"
:
"match"
,
//定义表名
"#primary"
:
"id"
,
//定义主键
"#db"
:
"localdb"
,
//定义存储的db类型,一般为localdb
"id"
:
"%018d"
,
//表主键,可以用交易总索引(txID())定义
"gameid"
:
"%018d"
,
//表外键,对应GameLocalTable表主键
"hash"
:
"%s"
,
//交易hash
"addr"
:
"%s"
,
//参与游戏的猜数字者的地址
}
this
.
defaultvalue
=
{
"id"
:
0
,
"gameid"
:
0
,
"hash"
:
""
,
"addr"
:
""
,
}
return
new
Table
(
kvc
,
this
.
config
,
this
.
defaultvalue
)
}
```
下面是创建MatchGameTable的代码
```
js
function
MatchGameTable
(
kvc
)
{
//参数lefttable是使用外键的表,数据量相对较大,参数index是需要查询连接表的字段名
return
new
JoinTable
(
MatchLocalTable
(
kvc
),
GameLocalTable
(
kvc
),
"addr#status"
)
}
```
#### 1.4.2 Init函数
```
js
function
Init
(
context
)
{
this
.
kvc
=
new
kvcreator
(
"init"
)
//创建init类型对象,用于存储key,value值
this
.
context
=
context
//保存上下文信息
return
this
.
kvc
.
receipt
()
}
```
#### 1.4.3 Exec的xxxx方法
此方法可以通过上面1.2的方法调用到,xxxx与payload.funcname值一致。主要作用是通过kvc.add()将一些信息写入stateDB,通过kvc.addlog()传递log信息。
```
js
Exec
.
prototype
.
NewGame
=
function
(
args
)
{
//
var
game
=
{
__type__
:
"game"
}
game
.
gameid
=
this
.
txID
()
game
.
height
=
this
.
context
.
height
game
.
randhash
=
args
.
randhash
game
.
bet
=
args
.
bet
game
.
hash
=
this
.
context
.
txhash
game
.
obet
=
game
.
bet
game
.
addr
=
this
.
context
.
from
game
.
status
=
1
//open
//最大值是 9000万,否则js到 int 会溢出
if
(
game
.
bet
<
10
*
COINS
||
game
.
bet
>
10000000
*
COINS
)
{
throwerr
(
"bet low than 10 or hight than 10000000"
)
}
if
(
this
.
kvc
.
get
(
game
.
randhash
))
{
//如果randhash 已经被使用了
throwerr
(
"dup rand hash"
)
}
var
err
=
this
.
acc
.
execFrozen
(
this
.
name
,
this
.
context
.
from
,
game
.
bet
)
//冻结庄家的奖池
throwerr
(
err
)
this
.
kvc
.
add
(
game
.
gameid
,
game
)
//存储key:gameid与value:game到stateDB
this
.
kvc
.
add
(
game
.
randhash
,
"ok"
)
//存储key:randhash与value:"ok"到stateDB
this
.
kvc
.
addlog
(
game
)
//传递game对象到ExecLocal.prototype.NewGame
return
this
.
kvc
.
receipt
()
}
```
#### 1.4.4 ExecLocal的xxxx方法
此方法将对应的Exec的xxxx方法传递的log信息写入到对应的表中,或者调用kvc.add()直接将数据写入stateDB。
```
js
function
localprocess
(
args
)
{
var
local
=
MatchGameTable
(
this
.
kvc
)
local
.
addlogs
(
this
.
logs
)
//操作join表,达到自动更新关联表的目的
local
.
save
()
//保存数据
return
this
.
kvc
.
receipt
()
}
ExecLocal
.
prototype
.
NewGame
=
function
(
args
)
{
return
localprocess
.
call
(
this
,
args
)
}
```
#### 1.4.5 Query的xxxx方法
此方法可以通过上面1.3的方法调用到,xxxx与payload.funcname值一致,实现按照传入参数查询数据的功能,可以通过表进行查询,也可以直接查询localDB。
```
js
//提供按照游戏创建者地址查询game信息
Query
.
prototype
.
ListGameByAddr
=
function
(
args
)
{
var
local
=
GameLocalTable
(
this
.
kvc
)
var
q
=
local
.
query
(
"addr"
,
args
.
addr
,
args
.
primaryKey
,
args
.
count
,
args
.
direction
)
return
querytojson
(
q
)
}
```
```
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment