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

作者: 陨石坠灭 | 来源:发表于2018-12-17 14:27 被阅读9次

    在调试合约的时候,发现部署合约是一件比较麻烦的事情,所以写了个脚本实现一键部署合约,写下这篇文章来介绍其中的一些命令。
    想要一键部署合约,编写脚本是必不可少的,这里以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

    
    async function 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

    async function 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

    async function 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

    async function 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为起始位置

    
    async function 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)
    }
    

    相关文章

      网友评论

        本文标题:EOS实践篇(续) - 合约一键部署

        本文链接:https://www.haomeiwen.com/subject/kokqkqtx.html