专栏首页全栈之路EOS实践篇(续) - 合约一键部署

EOS实践篇(续) - 合约一键部署

在调试合约的时候,发现部署合约是一件比较麻烦的事情,所以写了个脚本实现一键部署合约,写下这篇文章来介绍其中的一些命令。 想要一键部署合约,编写脚本是必不可少的,这里以Mac的Docker环境部署为例,脚本为shell脚本。本文将信息介绍部署的步骤,这样的话,在linux以及Windows上部署也可以作为参考。

前言

如果不了解EOS,可以先看:EOS实践篇

另外还有使用Scatter插件的教程:使用Scatter创建自己的账号

部署

步骤简介

  1. 下载docer
  2. 下载、启动以及配置keosd
  3. 创建钱包、导入密钥
  4. 打开钱包,结束钱包
  5. 创建账号
  6. 获取eos
  7. 购买cpu、net资源,购买ram资源
  8. 下载合约、将合约复制到docker实例目录下
  9. 设置合约
  10. 授权

步骤讲解

1. 下载docer

下载地址 https://www.docker.com/get-started

下载后根据提示进行安装,然后点击应用图标,即可启动docker服务

2. 下载、启动以及配置keosd

只有第一启动是需要执行一下步骤,下一次启动,只需要执行docker start keosd命令即可。

这里以shell脚本所在目录($curpath)为例,端口号为8900,也可以是其他的端口号:

docker pull eosio/eos
docker stop keosd
docker rm keosd

docker start keosd

curpath=$(cd "$(dirname "$0")"; pwd)

mkdir $curpath/eosio-wallet

docker run -d --restart=unless-stopped --name keosd   \
    -v $curpath/eosio-wallet:/opt/eosio/bin/data-dir  \
    -v $curpath/eosio-wallet:$home/eosio-wallet \
    -t eosio/eos /opt/eosio/bin/keosd  \
    --wallet-dir /opt/eosio/bin/data-dir \
    --http-server-address=127.0.0.1:8900

连接到测试网:

测试网接口可以为:https://jungle.eosn.io:443,同时接口可能会变动,可以访问如下网址,选择其中一个就行:

https://monitor.jungletestnet.io/#apiendpoints

另外,shopt -s expand_aliases这个命令的作用是使alias命令在脚本中也能生效。如果实在控制台下执行,就不需要改命令了。

alias命令设置后,就不需要每次执行命令都需要设置-u参数了,直接使用cleos命令即可。

docker start keosd
shopt  -s  expand_aliases
alias cleos="docker exec -i keosd /opt/eosio/bin/cleos  --wallet-url http://127.0.0.1:8900  -u <测试网接口>"

3. 创建钱包、导入密钥

这里需要提前生成私钥,可以使用命令cleos create key生成。另外创建钱包时,需要将生成的密码保存到文件中,以免忘记密码而导致钱包无法解锁。

wallet=<钱包名称>
pwd_file=<保存密码的文件>
private_key=<私钥>

cleos wallet create -n $wallet --to-console > $pwd_file

password=$(cat $pwd_file |grep "\"" |awk -F "\"" '{ print $2 }')
cat $password > $pwd_file

cleos wallet import -n $wallet $private_key

4. 打开钱包,解锁钱包

这是一个很烦恼的问题,每次交易的时候都会弹出需要解锁的提示,当你下一次交易时,你可以先解锁是,又会报错:提示已经解锁。虽然没什么,但是很烦。。。

以下是命令:

cleos wallet open -n <钱包名称>
cleos wallet unlock -n <钱包名称> --password <密码>

如果是默认(default)钱包,就不需要加-n参数了

5. 创建账号

https://monitor.jungletestnet.io/#account

进入网站后,只需要填写已注册的合约账号和公钥,然后验证,最后点击确认就可以

这里作为测试,owneractive的公钥可以填写为一样的。

当然,也可以通过命令来创建账号:

$ cleos system newaccount \
--stake-net '<网络资源要抵押的EOS数量> EOS' \
--stake-cpu 'cpu资源要抵押的EOS数量 EOS' \
--buy-ram-kbytes <要购买的ram字节数量> \
<金主账号> <新账号> <新账号公钥>

其中buy-ram-kbytes是字节,不需要带单位

6. 获取eos

https://monitor.jungletestnet.io/#faucet

进入网站后,只需要填写已注册的合约账号,然后验证,最后点击确认就可以获取EOS了,当然这是测试网的。

值得注意的是,同一个账号6小时内只有一次成功获取到EOS。这个可以提前获取,这样才能够已经部署合约,脚本执行过程中,不可能去获取EOS,这样就算不上是一键部署了。

7. 购买cpu、net资源,购买ram资源

如果要部署合约,需要购买cpu等资源,否则的话,会以部署失败告终,同时需要注意,购买的资源不够,同样会部署失败。

另外购买资源的账号与合约账号不一定是同一个账号,所以可以专门提供一个提供购买资源的账号,也就是金主,提前充值EOS到合约账号,这样就不需要中途去获取EOS,从而达到一键部署的目的。

购买cpu、net资源

如果只需要购买net资源,cpu资源抵押的EOS数量可为:0.0000,同理,只需要购买cpu资源类似。

$  cleos system delegatebw <金主账号> <新账号> "<网络资源要抵押的EOS数量> EOS" "<cpu资源要抵押的EOS数量> EOS"

购买ram

这里要提的一点是,填写的是bytes数量,所以不需要带单位。

$ cleos system buyram -k <金主账号> <新账号>  <bytes数量>

8. 下载和编译合约、将合约复制到docker实例目录下

下载合约:

如果合约在本地,就不需要下载合约了,这里以git为例:

if [ -d "<合约目录>" ];then
    cd <合约目录>/<合约名称>
    git pull
else
    mkdir <合约目录>
    cd <合约目录>
    git clone <git地址>
fi

如果需要切换分支:

这里的分支名称是指本地的分支名称

 git checkout <分支名称>

由于采用的是docker,所以需要考虑到合约路径的问题。需要将合约复制到docker目录下。

首先需要获取实例ID:

$ docker inspect -f '{{.ID}}'  keosd

复制合约:

doc_id=$(docker inspect -f '{{.ID}}'  keosd)
docker cp <当前合约路径> $doc_id:<实例目标合约路径>

9. 设置合约

设置新账号有一个关键的地方,就是合约路径。

这里的合约路径并不是本地的路径,而是docker实例中的路径,可以使用 docker exec -it keosd bash 命令,进入实例中查看合约所在的具体路径。

合约目录中需要包含*.wasm以及*.abi文件。

$ cleos set contract <新账号> <合约路径> -p <新账号>@active

10. 授权

这里的“授权”,是指使用户的账号能够拥有提现的权限。如果不需要提现功能的话,该步骤可以不做。

由于合约账号才拥有将EOS转出的权限,因此,合约如果要实现提现功能,似乎不可能,总不能把合约账号的私钥提供给用户吧。但是以下命令可以解决这个问题,合约提供提现的action方法,用户只需要自己的权限就可以提现。

$ cleos set account permission <新账号> active \
'{ \
  "threshold": 1, \
  "keys": [{"key": "<新账号>", "weight": 1}], \
  "accounts": [ \
      { \
          "permission": { "actor":"<新账号>","permission":"eosio.code" },
          "weight":1 \
      } \
  ] \
}' \
owner -p ${新账号}

脚本具体代码

代码放在github上了:

https://github.com/xiaoyifan6/soeth/blob/master/tools/eos/build.sh

另外介绍一下用法:

创建deploy.sh, 一下为空字符串的都是需要配置的变量,其中*_money为0,则表示不会进行此项购买或者抵押操作。

另外accountnew_account可以是同一个,前提是账号已经创建并且有充足的EOS。另外第二次部署合约,则为更新合约,若合约有改表或者action方法名称,可能需要消耗额外的ram,若abi文件没有变化,则不会消耗额外的ram。

contract_path这里是指合约的相对路径,相对于脚本所在目录的contracts目录。

#!/bin/bash
curpath=$(cd "$(dirname "$0")"; pwd)

account="" # 金主账号
new_account="" # 合约账号名称
contract_name="" # 合约名称

wallet="" # 钱包名称
ram_money="0" # 部署钱包需要的金额 1400
cpu_money="0" # 部署钱包需要的金额 4
net_money="0" # 部署钱包需要的金额 2
git_url="" # 合约git地址
private_key="" # 私钥
public_key="" # 公钥
contract_path="" # 合约相对路径
password="" #钱包密码
url="https://jungle.eosn.io:443"

# 切换到master分支
cd $curpath/contracts/$contract_name/
git checkout master

bash $curpath/build.sh \
    -a $account \
    -n $new_account \
    -g $git_url \
    -w $wallet \
    -C $contract_name \
    -P $password \
    -p $contract_path \
    -k $private_key \
    -K $public_key \
    -u $url \
    -r $ram_money \
    -c $cpu_money \
    -N $net_money 
    # -i #初始化

延伸

1. 获取chainId

以下命令可以直接得到当前节点的信息,其中包括chain_id

$ cleos get info

2. Scatter:切换账号

切换账号之前,需要先忘记账号。

var _scatter = window['scatter'];
_scatter && _scatter.forgetIdentity();
var defaultAccount = null;

var netwok = {
      blockchain: 'eos',
      host: <IP地址或url>,
      port: <端口号>,
      protocol: "<http|https>",
      chainId: "<ChainID>"
};

setTimeout(function () {
     _scatter.getIdentity({
          accounts: [netwok]
     }).then(res => {
           if (!res) return reject();
          var identity: Identity = res;
          var _account = identity.accounts.find((accound) => {
                return accound.blockchain == "eos";
          });
          defaultAccount = _account;
     }).catch(res => { });
}, 1000);

3. eosjs:获取账号信息,并计算netcpuram价格

首先初始化eos

var netwok = {
      blockchain: 'eos',
      host: <IP地址或url>,
      port: <端口号>,
      protocol: "<http|https>",
      chainId: "<ChainID>"
};

var eos =_scatter.eos(netwok, Eos, {}, <http|https>);

formatEos方法:

这里涉及到精度问题,这里只保留了6位小数。

function formatEos(value) {
    return parseFloat(parseFloat(value).toFixed(6));
}

getTableRows方法参考下面提到的分页查询。

计算net、cpu价格

account为账号,由于接收账号和付款账号可以是同一个,所以这里都填写为account。这里可以是defaultAccount.name

function async getNetResourceGetBandwidthPrice(account) {
    var res await eos.getAccount(account);
    var netPrice = 0;
    var cpuPrice = 0;
    if (res) {
        //1. 计算NET价格
        //抵押NET的EOS数量
        var netBalance = res.net_weight / 10000;
        //NET贷款的总量
        var netTotal = res.net_limit.max / 1024;
        //(netBalance / netTotal)获取到的是过去3天内的平均消耗量,除以3获取每天的平均消耗量,即价格
        netPrice = ((netBalance / netTotal) / 3);
        console.log(netBalance, netTotal, netPrice)

        //1. 计算CPU价格
        //抵押CPU的EOS数量
        var cpuBalance = res.cpu_weight / 10000;
        //CPU贷款的总量
        var cpuTotal = res.cpu_limit.max / 1024;
        //(cpuBalance / cpuTotal)获取到的是过去3天内的平均消耗量,除以3获取每天的平均消耗量,即价格
        cpuPrice = ((cpuBalance / cpuTotal) / 3);
    }

    return {
        netPrice: netPrice,
        cpuPrice: cpuPrice,
    };
}

计算ram价格

这里价格单位为:EOS/KB

function async getRamPrice() {
    var res = await getTableRows("eosio", "rammarket", "eosio",1,0);
    if (res && res.rows && res.rows.length > 0) {
        return formatEos(res.rows[0].quote.balance) / formatEos(res.rows[0].base.balance) * 1024;
    }
    return 0;
}

4. eosjs:购买netcpuram

首先,得写一个调用活动的方法:

defaultAccount:前面的账号切换提到defaultAccount的获取方法。

function doAction(contractName, actionName, ...param) {
    return new Promise((resolve, reject) => {
        const account = defaultAccount;
        const options = {
            authorization: [`${account.name}@${account.authority}`]
        };

       eos.contract(contractName).then(contract => {
            contract[actionName].apply(window, param.concat(options)).then(res2 => {
                resolve(res2);
            }).catch(err => {
                reject(err);
            });
        }).catch(err => {
            reject(err);
        });
    });
}

购买netcpu

这里的net_amount为购买网络资源需要抵押的EOS数量,cpu_amount为购买cpu资源需要抵押的EOS数量

account为账号,由于接收账号和付款账号可以是同一个,所以这里都填写为account。这里可以是defaultAccount.name

function async delegatebw(account, net_amount, cpu_amount) {
    let flag = true;
    try {
        var res = await doAction("eosio", "delegatebw", account, account, 
            `${net_amount.toFixed(4)} EOS`, `${cpu_amount.toFixed(4)} EOS`, 0);
      return res;
    } catch (e) {
        throw e;
    }
}

购买ram

这里ramAmount的单位是字节,即要购买的字节数量。

首先初始化eos:上面的获取账号信息有提到。

account为账号,由于接收账号和付款账号可以是同一个,所以这里都填写为account。这里可以是defaultAccount.name

function async buyRam(account, ramAmount) {
    let flag = true;
    try {
        var res = await doAction("eosio", "buyrambytes", account, account, ramAmount);
      return res;
    } catch (e) {
        throw e;
    } 
}

5. eosjs:关于分页获取合约表格数据

分页查询一般有两种方式,一个是知道第几页以及每页大小或者其起始索引以及每页大小。

而今天讲的是后者。

首先初始化eos:上面的获取账号信息有提到。

然后编写函数,其中limit为每页大小,index_position为起始位置

function async getTableRows(contractName, table, scope, limit, index_position) {
    try {
      var start = new Date().getTime();
      var flag = true;
      var param = {
         code: contractName,
         scope: scope,
         table: table,
        json: true,
        lower_bound: index_position,
     };
     limit && (param["limit"] = limit);
     if (!eos) {
       return {
          rows: [],
          more: false
       };
   }
   return await eos.getTableRows(param);
  } catch (e) {
      throw e;
  }
}

6. eosjs:关于uint64_t的编码与解码

uint64_t在合约中可以将字符串类型存储到表中,而这时候客户端获取到的是一个数值,通过解码后,可以得到相应的字符串。

function encode(value) {
      return Eos["modules"].format.encodeName(value, false);
}

function decode(value) {
    return Eos["modules"].format.decodeName(value.toString(), false)
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • vue动态加载远程js完美实践

    其实vue加载远程js的教程很多,但是我比较笨呐。。。用不出来,每次都报方法/属性不存在的错误,来看一下典型的写法:

    陨石坠灭
  • 使用Scatter创建自己的账号

    官网:https://get-scatter.com/ Chrome应用商店:https://chrome.google.com/webstore/search...

    陨石坠灭
  • 关于tomcat读取文件出现乱码的问题

    以前只知道需要在web.xml里面配置filter,今天发现了一个致命的问题,就是tomcat运行时读取文件出现中文乱码,本地运行main函数却又没有乱码。

    陨石坠灭
  • C++拷贝构造函数

         拷贝构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构建及初始化。其唯一的参数(对象的引用)是不可变的(const类型)...

    猿人谷
  • C++拷贝构造函数(深拷贝,浅拷贝)

    对于普通类型的对象来说,它们之间的复制是很简单的,例如:   int a=88;   int b=a;   而类对象与普通对象不同,类对象...

    _gongluck
  • python pyqt5 设置控件随窗体拉伸

    from PyQt5.QtWidgets import QApplication ,QWidget, QVBoxLayout , QHBoxLayout ,Q...

    用户5760343
  • python数据分析-第一讲:工作环境及本地数据文件

    Json是一种轻量级的数据交换格式。Json源自JavaScript语言,易于人类的阅读和编写,同时也易于机器解析和生成,是目前应用最广泛的数据交换格式。 J...

    小海怪的互联网
  • 树莓派上使用docker部署aria2,minidlna

    目前在树莓派上安装aria2跟minidlna能搜到的教程基本上都是直接apt-get install安装的。现在是docker的时代了,其实这2个东西可以直接...

    kklldog
  • 评分卡模型开发-用户数据缺失值处理

    在我们搜集样本时,许多样本中一般都含有缺失值,这种情况在现实问题中非常普遍,这会导致一些不能处理缺失值的分析方法无法应用,因此,在信用风险评级模型开发的第一步我...

    Erin
  • 基于图像的场景三维建模

    三月已过半旬,已是春暖花开的季节,也是我们科研爱好者最繁忙的一段时间。春天的到来,意味着新一届的学子即将离开学校,走向自己人生的第二段道路,也意味着您年伊始,所...

    计算机视觉研究院

扫码关注云+社区

领取腾讯云代金券