以传统的WEB开发方式,来举例理解Dapp开发
前言
常规的讲解DApp开发的文章,大都直接从区块链方式讲起,对于没有接触过区块链的同学,可能不太好理解,今天我们尝试从传统的web开发,对比讲解DApp开发。
对于目前常见的DApp大都是以Web的形式,其实DApp并不局限于Web形式,也可以是PC客户端,也可以是App。为了更好理解和演示,我们将以常见的Web形式的DApp讲起。
演示实例
我们将要演示的DApp例子为,一个计算器,合约中计算两个输入的值,并将值存入链上,并提供查询的接口。
如果我们拿常规的Web去实现下这个App。基本分为3部分,前端(获取输入的值),服务器Api接收程序(接收前端输入的值,并计算结果,并将结果写入数据库,以及提供查询的接口),数据库(存储计算后的值)。
实现和部署对比
实现 | Web App | Web DApp |
---|---|---|
前端 | 普通html | 普通html |
服务器Api接收程序 | php开发的api程序 | 执行链上部署的合约 |
数据库 | mysql | 写入链上对应的RAM |
服务器部署 | 云服务器 | 前端放在云服务器,合约部署在EOS网络 |
传统Web实现
1.前端
<form method="post" name="form" action="http://www.xxx.xx/postGet.php">
<table >
<tr>
<td>第一个参数:</td>
<td><input type="text" name="FirstParm"/></td>
</tr>
<tr>
<td>第二个参数:</td>
<td><input type="text" name="SecondParm"/></td>
</tr>
<tr>
<td><input type="submit" name="Submit" value="计算两数和"/></td>
</tr>
</table>
</form>
2.创建数据库
CREATE DATABASE results;
CREATE TABLE result(name CHAR(13), iresult int);
3.Api接收程序
<?php
$firstparm = $_POST[FirstParm'];
$secondparm = $_POST['SecondParm'];
$result = $firstparm + $secondparm;
$con = mysql_connect("localhost","cryptokylinq","5KKu8Nmm4XUavcZFzpvdGG5Mc9Rkg6LiwxuqXkSokEHKSXKXs9b");
if (!$con) {
die('Could not connect: ' . mysql_error());
}
mysql_select_db("results", $con);
mysql_query("INSERT INTO result (iresult) VALUES ($result)");
mysql_close($con);
?>
查询
<?php
$con = mysql_connect("localhost","cryptokylinq","5KKu8Nmm4XUavcZFzpvdGG5Mc9Rkg6LiwxuqXkSokEHKSXKXs9b");
if (!$con) {
die('Could not connect: ' . mysql_error());
}
mysql_select_db("results", $con);
$result = mysql_query("SELECT * FROM result");
while($row = mysql_fetch_array($result)) {
echo $row['iresult'] ;
echo "<br />";
}
mysql_close($con);
?>
上面三步简单的完成了,常规Web的计算,写入,查询操作。
Web DApp实现
1.编写合约
//Calculator.hpp
#include <eosiolib/asset.hpp>
#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>
class Calculator : public eosio::contract {
public:
Calculator(account_name self)
: eosio::contract(self)
,results(_self, _self){}
public:
void doit(account_name from, const uint64_t firstParm, const uint64_t secoundParm);
private:
//@abi table results i64
struct result{
account_name name;
uint8_t iresult;
uint64_t primary_key()const { return name; }
EOSLIB_SERIALIZE(result, (name)(iresult))
};
typedef eosio::multi_index<N(results),result> result_table;
result_table results;
};
EOSIO_ABI( Calculator, (doit) )
#include "Calculator.hpp"
void Calculator::doit(account_name from, const uint64_t firstParm, const uint64_t secoundParm){
require_auth(from);
eosio_assert(firstParm >= 1, "firstParm mast >= 0"); //没有实际意义 只是做个数值的判断演示
auto account_itr = results.find(from);
if(account_itr == results.end()){
account_itr = results.emplace(_self, [&](auto& result){
result.name = from;
result.iresult = firstParm + secoundParm;
});
}
else{
results.modify(account_itr, 0, [&](auto& result) {
result.iresult = firstParm + secoundParm;
});
}
}
合约中包含了数据库的创建格式,合约部署后,相当于,数据库也创建好了,以及数据写入需要执行的api接口
2.合约编译,需要将合约初步编译,才能部署到链上
- 生成API文件
suroudeMacBook-Pro:Calculator surou$ eosiocpp -g Calculator.abi Calculator.cpp
- 生成WAST文件
suroudeMacBook-Pro:Calculator surou$ eosiocpp -o Calculator.wast Calculator.cpp
执行完成后目录文件如下
suroudeMacBook-Pro:Calculator surou$ ls
Calculator.abi Calculator.cpp Calculator.hpp Calculator.wasm Calculator.wast
3.创建麒麟测试网的测试账号,用于合约的部署,以及使用资源的购买
比如我们要创建的账号为cryptokylinq
,访问链接:http://faucet.cryptokylin.io/create_account?cryptokylinq
{
msg: "succeeded",
keys: - {
active_key: - {
public: "EOS6L7pr3AaKekTs1dbratDq1PutoSdmBWJFwbLcStsnKBbJtNUws",
private: "5KKu8Nmm4XUavcZFzpvdGG5Mc9Rkg6LiwxuqXkSokEHKSXKXs9b"
},
owner_key: - {
public: "EOS89vNXgGrkM4GYFAav78FgnX94QzVXYtcEKiKicSq4YcykPmnC7",
private: "5KZbLRwZAZQJQKS6sM1RL1d2Af9s49zA9qka9f63ce7J2jUDWwQ"
}
},
account: "cryptokylinq"
}
一个账号一般有两对秘钥,对应两个权限owner(类似网站后台的超级管理员)和active(部分权限的管理员),当然还可以创建多个自由配置权限的秘钥对。在EOS网络里,谁拥有这个私钥,谁就拥有对应账号的对应权限。相同的秘钥对可以创建多个账号。
- 申请测试的EOS代币,访问 http://faucet.cryptokylin.io/get_token?cryptokylinq
{
msg: "succeeded"
}
申请来的测试EOS代币,将用于购买EOS网络上的CPU,NET,RAM资源(RAM为购买,CPU和NET是按抵押EOS多少,计算分配你多少资源使用)。相当于云服务器中的增加对应的硬件资源。CPU和NET与云服务中的一样,计算和网络资源。RAM相当于云服务器中的RAM+磁盘存储,比如发起交易时会临时消耗,隔一段时间(暂时未确定恢复时间的规则,应该小于三天,稍后查证后,在做补充),如果是合约存储数据,那就相当于写入了磁盘,如果数据不删除的话,会持续占用。
下面开始购买所需的资源,之前统计一般部署合约需要消耗300k左右的 RAM(包含临时消耗)。 CPU和NET基本分别抵押 0.1EOS就可以了。
由于我们要操作账号,所以我们要先创建个钱包,可以使用eosio 自带的keosd,也可以使用其他三方的app 钱包中购买资源。
我们在开发环境,所以直接使用keosd操作了,
- 创建钱包
suroudeMacBook-Pro:eosio-wallet surou$ cleos wallet create --to-console
Creating wallet: default
Save password to use in the future to unlock this wallet.
Without password imported keys will not be retrievable.
"PW5HxNY7tKndZp8c96afmhaHezq2CA9x1dbziYXzJcvK7sVdf7kQq"
记得备份这个钱包的解锁密码"PW5HxNY7tKndZp8c96afmhaHezq2CA9x1dbziYXzJcvK7sVdf7kQq"
- 将上面创建的账号cryptokylinq的私钥导入新创建的钱包 (一般常规操作只需要导入active权限的私钥)
suroudeMacBook-Pro:eosio-wallet surou$ cleos wallet import
private key: imported private key for: EOS6L7pr3AaKekTs1dbratDq1PutoSdmBWJFwbLcStsnKBbJtNUws
导入完成后,购买所需的RAM,CPU,NET资源
购买10EOS的RAM
suroudeMacBook-Pro:contracts surou$ cleos -u http://kylin.fn.eosbixin.com system buyram cryptokylinq cryptokylinq "10 EOS"
executed transaction: d59370bcb3dbf1d3aaa1b5c92de23283603c6d9bfc2e38e8def5e0de8eabe6e1 128 bytes 1511 us
# eosio <= eosio::buyram {"payer":"cryptokylinq","receiver":"cryptokylinq","quant":"10.0000 EOS"}
# eosio.token <= eosio.token::transfer {"from":"cryptokylinq","to":"eosio.ram","quantity":"9.9500 EOS","memo":"buy ram"}
# cryptokylinq <= eosio.token::transfer {"from":"cryptokylinq","to":"eosio.ram","quantity":"9.9500 EOS","memo":"buy ram"}
# eosio.ram <= eosio.token::transfer {"from":"cryptokylinq","to":"eosio.ram","quantity":"9.9500 EOS","memo":"buy ram"}
# eosio.token <= eosio.token::transfer {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.0500 EOS","memo":"ram fee"}
# cryptokylinq <= eosio.token::transfer {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.0500 EOS","memo":"ram fee"}
# eosio.ramfee <= eosio.token::transfer {"from":"cryptokylinq","to":"eosio.ramfee","quantity":"0.0500 EOS","memo":"ram fee"}
CPU NET分别抵押10 EOS (实际使用量,一般在分别1 EOS就够了,测试网不花钱,可劲豪)
suroudeMacBook-Pro:contracts surou$ cleos -u http://kylin.fn.eosbixin.com system delegatebw cryptokylinq cryptokylinq "10 EOS" "10 EOS"
executed transaction: 4df295028046be07f669fd3dca1b496434f5ee36efb0cd6f31a738ff6e7b5602 144 bytes 2021 us
# eosio <= eosio::delegatebw {"from":"cryptokylinq","receiver":"cryptokylinq","stake_net_quantity":"10.0000 EOS","stake_cpu_quant...
# eosio.token <= eosio.token::transfer {"from":"cryptokylinq","to":"eosio.stake","quantity":"20.0000 EOS","memo":"stake bandwidth"}
# cryptokylinq <= eosio.token::transfer {"from":"cryptokylinq","to":"eosio.stake","quantity":"20.0000 EOS","memo":"stake bandwidth"}
# eosio.stake <= eosio.token::transfer {"from":"cryptokylinq","to":"eosio.stake","quantity":"20.0000 EOS","memo":"stake bandwidth"}
此时RAM,CPU,NET资源都准备好了,相当于云服务器硬件配置都买好了
下面开始部署合约,相当于传统的Web开发,将API程序部署到云服务器
suroudeMacBook-Pro:contracts surou$ cleos -u http://kylin.fn.eosbixin.com set contract cryptokylinq Calculator/ -p
Reading WASM from Calculator/Calculator.wasm...
Publishing contract...
executed transaction: f0086795a65644a96e282b165e1fc146869a37cfd6a2e51528367f7594d4d21f 3056 bytes 1329 us
# eosio <= eosio::setcode {"account":"cryptokylinq","vmtype":0,"vmversion":0,"code":"0061736d01000000015d1060047f7e7e7e0060000...
# eosio <= eosio::setabi {"account":"cryptokylinq","abi":"0e656f73696f3a3a6162692f312e30000206726573756c740002046e616d65046e6...
下一步我们直接测下合约action接口,相当于直接测试云服务器的API接口
suroudeMacBook-Pro:contracts surou$ cleos -u http://kylin.fn.eosbixin.com push action cryptokylinq doit '["cryptokylinq","1","2"]' -p cryptokylinq
executed transaction: 7c0de68f77a656f98cd54e03f37dbb00a1a325cee844614968d7d05a2668f36c 120 bytes 1834 us
# cryptokylinq <= cryptokylinq::doit {"from":"cryptokylinq","firstParm":1,"secoundParm":2}
查询下结果
suroudeMacBook-Pro:contracts surou$ cleos -u http://kylin.fn.eosbixin.com get table cryptokylinq cryptokylinq results
{
"rows": [{
"name": "cryptokylinq",
"iresult": 3
}
],
"more": false
}
可以看到,已经将结果写到了链上 "iresult": 3
,相当于常规Web程序数据库中已存入数据
目前此DApp对比常规Web程序已经完成了,API和数据库部分,下面开始完成前端的调用
DApp前端
前端与链交互,是通过rpc进行操作的,并且eosio 官方也提供了 eosjs.js 开发库,方便操作。
html中调用的关键js代码为
import EOS from 'eosjs'
const EOS_CONFIG = {
contractName: "cryptokylinq", // Contract name 更好理解的应该是合约的部署所在的账号名
contractReceiver: "cryptokylinq", // User executing the contract (should be paired with private key)
clientConfig: {
keyProvider: '5KKu8Nmm4XUavcZFzpvdGG5Mc9Rkg6LiwxuqXkSokEHKSXKXs9b', // Your private key
httpEndpoint: 'http://api-kylin.starteos.io', // EOS http endpoint
chainId: '5fff1dae8dc8e2fc4d5b23b2c7665c97f9e9d8edf2b6485a86ba311c25639191'
}
}
//执行合约
DoContract(){
let eosClient = EOS(EOS_CONFIG.clientConfig)
eosClient.contract(EOS_CONFIG.contractName)
.then((contract) => {
//执行链上合约
contract.doit(EOS_CONFIG.contractReceiver,"1","2", { authorization: [EOS_CONFIG.contractReceiver] })
.then((res) => { console.log("Success") })
.catch((err) => {console.log("Fail"); console.log(err) })
})
}
查询结果
eos.getTableRows(true,"cryptokylinq","cryptokylinq","results")
返回
{
"rows":[
{
"name":"cryptokylinq",
"iresult":3
}
],
"more":false
}
至此完成了DApp从合约编写,合约部署,账号创建,合约调用,eosjs使用,一整套简单的流程。