Commit 0c84ad7b authored by Hugo's avatar Hugo

add js合约

parent 0af31f0b
## 1 jsvm 接口
## 1 jsvm 接口
[TOC]
### 1.1 部署一个jsvm合约 CreateTransaction
> 这里创建的是原始交易,除此之外,还需要进行交易的签名以及发送操作
> 如果合约中要使用数字货币,在调用合约前需要先向合约充值
**请求报文:**
```json
{
"jsonrpc":"2.0",
"method": "Chain33.CreateTransaction",
"params": [
{
"execer": "string",
"actionName": "string",
"payload": {
"code": "string",
"name":"string"
}
}
],
"id":int32,
}
```
**参数说明:**
|参数|类型|是否必填|说明|
|----|----|----|----|----|
|execer|string|是|执行器名称,这里固定为jsvm|
|actionName|string|是|操作名称,这里固定为Create|
|payload.code|string|是|javascript代码|
|payload.name|string|是|合约名|
**响应报文:**
```json
{
"id":int32,
"result":"string",
"error":null
}
```
**参数说明:**
|参数|类型|说明|
|----|----|----|
|result|string|交易内容十六进制字符串|
**示例:**
Request1 部署合约交易:
```json
{
"method": "Chain33.CreateTransaction",
"params": [
{
"execer": "jsvm",
"actionName": "Create",
"payload": {
"code": "//........................\n//............: ............... 0 - 10 ......... hash(......... + 9) (.....................) NewGame()\n//............................................................ Guess()\n//...... CloseGame()\nfunction Init(context) {\n this.kvc = new kvcreator(\"init\")\n this.context = context\n return this.kvc.receipt()\n}\n\nvar MIN_WAIT_BLOCK = 2\nvar RAND_MAX = 10\n\nfunction ExecInit() {\n this.acc = new account(this.kvc, \"coins\", \"bty\")\n}\n\nExec.prototype.NewGame = function(args) {\n var game = {__type__ : \"game\"}\n game.gameid = this.txID()\n game.height = this.context.height\n game.randhash = args.randhash\n game.bet = args.bet\n game.hash = this.context.txhash\n game.obet = game.bet\n game.addr = this.context.from\n game.status = 1 //open\n //............ 9000...,......js... int .........\n if (game.bet < 10 * COINS || game.bet > 10000000 * COINS) {\n throwerr(\"bet low than 10 or hight than 10000000\")\n }\n if (this.kvc.get(game.randhash)) { //......randhash ..................\n throwerr(\"dup rand hash\")\n }\n var err = this.acc.execFrozen(this.name, this.context.from, game.bet)\n throwerr(err)\n this.kvc.add(game.gameid, game)\n this.kvc.add(game.randhash, \"ok\")\n this.kvc.addlog(game)\n return this.kvc.receipt()\n}\n\nExec.prototype.Guess = function(args) {\n var match = {__type__ : \"match\"}\n match.gameid = args.gameid\n match.bet = args.bet\n match.id = this.txID()\n match.addr = this.context.from\n match.hash = this.context.txhash\n match.num = args.num\n var game = this.kvc.get(match.gameid)\n if (!game) {\n throwerr(\"guess: game id not found\")\n }\n if (game.status != 1) {\n throwerr(\"guess: game status not open\")\n }\n if (this.context.from == game.addr) {\n throwerr(\"guess: game addr and match addr is same\")\n }\n if (match.bet < 1 * COINS || match.bet > game.bet / RAND_MAX) {\n throwerr(\"match bet litte than 1 or big than game.bet/10\")\n }\n var err = this.acc.execFrozen(this.name, this.context.from, match.bet)\n console.log(this.name, this.context.from, err)\n throwerr(err)\n this.kvc.add(match.id, match)\n this.kvc.addlog(match)\n return this.kvc.receipt()\n}\n\nExec.prototype.CloseGame = function(args) {\n var local = MatchLocalTable(this.kvc)\n var game = this.kvc.get(args.gameid)\n if (!game) {\n throwerr(\"game id not found\")\n }\n var querykey = local.get(\"gameid\", args)\n var matches = local.query(\"gameid\", querykey, \"\", 0, 1)\n if (!matches) {\n matches = []\n }\n var n = -1\n for (var i = 0; i < RAND_MAX; i++) {\n if (Sha256(args.randstr + i) == game.randhash) {\n n = i\n }\n }\n if (n == -1) {\n throwerr(\"err rand str\")\n }\n //.........................................................\n if (this.context.height - game.height < MIN_WAIT_BLOCK) {\n throwerr(\"close game must wait \"+MIN_WAIT_BLOCK+\" block\")\n }\n for (var i = 0; i < matches.length; i++) {\n var match = matches[i].left\n if (match.num == n) {\n //................................................................................................ this\n win.call(this, game, match)\n } else {\n fail.call(this, game, match)\n }\n }\n if (game.bet > 0) {\n var err = this.acc.execActive(this.name, game.addr, game.bet)\n throwerr(err)\n game.bet = 0\n }\n game.status = 2\n this.kvc.add(game.gameid, game)\n this.kvc.addlog(game)\n return this.kvc.receipt()\n}\n\nfunction win(game, match) {\n var amount = (RAND_MAX - 1) * match.bet\n if (game.bet - amount < 0) {\n amount = game.bet\n }\n var err \n if (amount > 0) {\n err = this.acc.execTransFrozenToActive(this.name, game.addr, match.addr, amount)\n throwerr(err, \"execTransFrozenToActive\")\n game.bet -= amount\n }\n err = this.acc.execActive(this.name, match.addr, match.bet)\n throwerr(err, \"execActive\")\n}\n\nfunction fail(game, match) {\n var amount = match.bet\n err = this.acc.execTransFrozenToFrozen(this.name, match.addr, game.addr, amount)\n throwerr(err)\n game.bet += amount\n}\n\nExec.prototype.ForceCloseGame = function(args) {\n var local = new MatchLocalTable(this.kvc)\n var game = this.kvc.get(args.id)\n if (!game) {\n throwerr(\"game id not found\")\n }\n var matches = local.getmath(args.id)\n if (!matches) {\n matches = []\n }\n if (this.context.height - game.height < 100) {\n throwerr(\"force close game must wait 100 block\")\n }\n for (var i = 0; i < matches.length; i++) {\n var match = matches[i]\n win.call(this.kvc, game, match)\n }\n if (game.bet > 0) {\n var err = this.acc.execActive(this.name, game.addr, game.bet)\n throwerr(err)\n game.bet = 0\n }\n game.status = 2\n this.kvc.add(game.gameid, game)\n this.kvc.addlog(game)\n return this.kvc.receipt()\n}\n\nExecLocal.prototype.NewGame = function(args) {\n return localprocess.call(this, args)\n}\n\nExecLocal.prototype.Guess = function(args) {\n return localprocess.call(this, args)\n}\n\nExecLocal.prototype.CloseGame = function(args) {\n return localprocess.call(this, args)\n}\n\nExecLocal.prototype.ForceCloseGame = function(args) {\n return localprocess.call(this, args)\n}\n\nfunction localprocess(args) {\n var local = MatchGameTable(this.kvc)\n local.addlogs(this.logs)\n local.save()\n return this.kvc.receipt()\n}\n\nQuery.prototype.ListGameByAddr = function(args) {\n var local = GameLocalTable(this.kvc)\n var q = local.query(\"addr\", args.addr, args.primaryKey, args.count, args.direction)\n return querytojson(q)\n}\n\nQuery.prototype.ListMatchByAddr = function(args) {\n var local = MatchGameTable(this.kvc)\n var q= local.query(\"addr#status\", args[\"addr#status\"], args.primaryKey, args.count, args.direction)\n return querytojson(q)\n}\n\nfunction GameLocalTable(kvc) {\n this.config = {\n \"#tablename\" : \"game\",\n \"#primary\" : \"gameid\",\n \"#db\" : \"localdb\",\n \"gameid\" : \"%018d\",\n \"status\" : \"%d\",\n \"hash\" : \"%s\",\n \"addr\" : \"%s\",\n }\n this.defaultvalue = {\n \"gameid\" : 0,\n \"status\" : 0,\n \"hash\" : \"\",\n \"addr\" : \"\",\n }\n return new Table(kvc, this.config, this.defaultvalue) \n}\n\nfunction MatchLocalTable(kvc) {\n this.config = {\n \"#tablename\" : \"match\",\n \"#primary\" : \"id\",\n \"#db\" : \"localdb\",\n \"id\" : \"%018d\",\n \"gameid\" : \"%018d\",\n \"hash\" : \"%s\",\n \"addr\" : \"%s\",\n }\n this.defaultvalue = {\n \"id\" : 0,\n \"gameid\" : 0,\n \"hash\" : \"\",\n \"addr\" : \"\",\n }\n return new Table(kvc, this.config, this.defaultvalue) \n}\n\nfunction MatchGameTable(kvc) {\n return new JoinTable(MatchLocalTable(kvc), GameLocalTable(kvc), \"addr#status\")\n}\n",
"name": "game"
}
}
],
"id": 0
}
```
### 1.2 调用创建的jsvm合约的方法 CreateTransaction
**请求报文:**
```json
{
"jsonrpc":"2.0",
"id":0,
"method": "Chain33.CreateTransaction",
"params": [
{
"execer": "string",
"actionName": "string",
"payload": {
"name": "string",
"funcname": "string",
"args": "string"
}
}
],
}
```
**参数说明:**
|参数|类型|说明|
|----|----|----|
|execer|string|合约名字,固定格式为user.jsvm.xxxx|
|actionName|string|调用,这里固定为Call|
|payload.name|string|合约名字,之前定义的名字|
|payload.funcname|string|调用的方法名|
|payload.args|string|参数列表|
**响应报文:**
```json
{
"id":int32,
"result":"string",
"error":null
}
```
**参数说明:**
|参数|类型|说明|
|----|----|----|
|result|string|交易内容十六进制字符串|
**示例:**
Request:
```json
{
"jsonrpc":"2.0",
"id":0,
"method": "Chain33.CreateTransaction",
"params": [
{
"execer": "user.jsvm.game",
"actionName": "Call",
"payload": {
"name": "game",
"funcname": "NewGame",
"args": "{\"bet\":10000000000, \"randhash\":\"0x5d2ac15654aa1859af80d1dfa5d7a5775c359fe3a49abce03796f4fbc313b57f\"}"
}
}
],
}
```
Response:
```json
{
"id": 0,
"result": "0a0e757365722e6a73766d2e67616d651279180112750a0467616d6512074e657747616d651a647b22626574223a31303030303030303030302c202272616e6468617368223a22307835643261633135363534616131383539616638306431646661356437613537373563333539666533613439616263653033373936663466626333313362353766227d20a08d0630ae97b68eead9ec99083a2231356d6a48594e76506b534d72703353517874637a584c3663696e4d414564654b73",
"error": null
}
```
### 1.3 查询创建的jsvm合约
**请求报文:**
```json
{
"jsonrpc":"2.0",
"method": "Chain33.Query",
"params": [
{
"execer": "string",
"funcName": "string",
"payload": {
"name": "string",
"funcname": "string",
"args": "{\"addr\":\"14KEKbYtKKQm4wMthSK9J4La4nAiidGozt\", \"count\":20}"
}
}
],
"id": 0
}
```
**参数说明:**
|参数|类型|是否必填|说明|
|----|----|----|----|----|
|execer|string|是|执行器名称,这里固定为jsvm|
|funcName|string|是|操作名称,这里固定为Query|
|payload.name|string|是|合约名|
|payload.funcname|string|是|javascript中定义的查询方法名|
|payload.args|string|是|javascript中定义的查询方法的参数列表,用json格式表示|
**响应报文:**
```json
{
"id": int32,
"result": {
"data": "string"
},
"error": null
}
```
**参数说明:**
|参数|类型说明|
|----|----|----|----|
|result.data|string|查询结果|
**示例:**
Request:
```json
{
"method": "Chain33.Query",
"params": [
{
"execer": "jsvm",
"funcName": "Query",
"payload": {
"name": "game",
"funcname": "ListGameByAddr",
"args": "{\"addr\":\"14KEKbYtKKQm4wMthSK9J4La4nAiidGozt\", \"count\":20}"
}
}
],
"id": 0
}
```
Response:
```json
{
"id": 0,
"result": {
"data": "[{\"left\":{\"__type__\":\"game\",\"addr\":\"14KEKbYtKKQm4wMthSK9J4La4nAiidGozt\",\"bet\":10000000000,\"gameid\":121400000,\"hash\":\"0xd3894f7ab60a535d8de47f8a5e06d7c127a22bfe9ff8f1e00971fae19091126f\",\"height\":1214,\"obet\":10000000000,\"randhash\":\"0x5d2ac15654aa1859af80d1dfa5d7a5775c359fe3a49abce03796f4fbc313b57f\",\"status\":1}}]"
},
"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
# 一、接口描述
# 一、接口描述
## 1 发布事件
### 1.1 生成发布事件的交易(未签名)
**请求报文:**
```json
{
"jsonrpc":"2.0",
"method": "Chain33.CreateTransaction",
"params": [
{
"execer": "oracle",
"actionName": "EventPublish",
"payload": {
"type": "string",
"subType": "string",
"time": int64,
"content": "string",
"introduction": "string"
}
}
],
"id": int32
}
```
**参数说明:**
|参数|类型|说明|
|----|----|----|
|type|string|事件类型|
|subType|string|事件子类型|
|time|int64|事件结果预计公布时间,UTC时间|
|content|string|事件内容,例如可以用json格式表示|
|introduction|string|事件介绍|
**响应报文**
```json
{
"id": int32,
"result": "string",
"error": null
}
```
**参数说明:**
|参数|类型|说明|
|----|----|----|
|result|string|交易十六进制编码后的字符串|
**示例**
Request:
```json
{
"method": "Chain33.CreateTransaction",
"params": [
{
"execer": "oracle",
"actionName": "EventPublish",
"payload": {
"type": "football",
"subType": "Premier League",
"time": 1548084600,
"content": "test",
"introduction": "test2"
}
}
],
"id": 0
}
```
Response:
```json
{
"id": 0,
"result": "0a066f7261636c65123138010a2d1208666f6f7462616c6c1a0e5072656d696572204c656167756520f8ca97e2052a04746573743205746573743220a08d06309a95a2ce81b6aabf1d3a22314350794a7152427a4c4b537a504c44416845454d64666f53464450513965477448",
"error": null
}
```
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment