您正在查看: EOS 分类下的文章

怎么给超级节点发工资:EOS的奖励分配规则

最近几篇文章,持续在聊EOS的资源分配模型。今天节后上班第一天,我们来聊个五块钱的天,看看EOS节点的收入问题:收益有多少,是怎么计算出的。

当下一次有人问你,EOS的通胀比率是多少,节点的奖励比例是多少,怎么分配的时候,也许这篇文章可以帮你回答这些问题。

主要用到的网站:

超级节点/备用节点/BP是什么?

在许多文章里面你会看到BP这一缩写。这是Block Producer的简称,即出块节点。

实际上,超级节点,这是中文里面才会有的称呼,似乎不加上超级,就显得不够霸气似的。英文语境里面,只是称呼EOS的节点为BP(Block Producer)。

节点分为两类:出块节点,跟备用节点(Block Producer Candidate)。在EOS主网之中,现在是设定为前21个节点为出块节点,其他的节点为备用节点。

备用节点也有另外的名称,叫做候选节点,两个名字都是说的同一回事。

你如果愿意,也可以调用EOS的命令,将自己启动的节点注册为节点,只是,未必有收益罢了。

节点收益知多少?

通过https://eos.host/mainnet这一网络,可以非常直观的查看各个节点的收益情况。

上图列出了前30个节点的得票率占比,橙色为出块节点,灰色为备用节点。

21个出块节点的收益


通过第二张图,可以看到,有一栏是列出来了节点的预估收益。

我们再仔细看一下。排名第一的佳能节点(恭喜佳能!),总计出块34895,预估得到收益为: 1,248.86 EOS

备用节点的收益


除了出块节点之外,也有部分备用节点是有收益的。从图中可以看出来,排名59的备选节点hexlantttttt,预计也能够得到100多的EOS作为回报。

你可能会好奇:出块节点的收益跟备用节点的收益是怎么算的呢?我们从EOS的增发开始说起。

算算节点的收益分配

分配的流程
每次有节点发起指令要领取奖励的时候,系统会计算:

  • 新增发的EOS总数有多少;
  • 按照比例,将新增发的EOS分配到不同的奖励池之中(下文会详述);
  • 按照节点的实际情况,计算节点应该得到的收益,并分配这部分收益给节点;
  • 对于备用节点来说,如果领取的奖励不足100个EOS,则无法领取奖励。
    简单概括就是,先计算各部分奖励池应该分配多少EOS,即新注入多少EOS;然后,根据规则,分配这部分新增的奖励,分配给对应的人节点。

EOS通胀率为5%

EOS是温和通胀设计的系统,每年增发的EOS比率为5%左右,按照总供应量10亿来计算,就是五千万EOS。
而节点得到的奖励,只是通胀的一小部分,只是总EOS数量的1%。

新增EOS的用途

实际上,EOS的增发,是持续进行的,而并非是每年一次性新增加EOS。每次EOS的节点发起领取奖励的命令,就会计算新增EOS的量。

通胀的EOS,有两个用途:

  • 通胀EOS的20%, 用于给节点的奖励,即,如果新增了五千万EOS,那么会有1千万EOS分配给节点作为奖励,包括给出块节点和备用节点;
  • 通胀EOS的80%, 用于EOS基金池,这部分会在未来用于Worker Proposal的奖励发放。所谓的Worker Proposal,是指的社区福利应用或者对EOS系统有所帮助的项目。

Worker Proposal这部分的奖励,现在已经开始累积,但是没有发放。

后续会部署新的智能合约到系统之中,经过持票人投票选出来认为对社区有益的DAPP,来获得这部分奖金。

可以看得出来,EOS设计之中对于生态的重视,Worker Proposal(可以称为:工作提案?)这部分每年足足有四千万左右的EOS奖励。(此处进行了简化,实际上随着EOS通胀,奖励的EOS数量会越来越多,比率为总流通量的4%)

如果你觉得自己的创意够牛逼,对于推动社区进步有贡献,那后续,光凭藉Worker Proposal的这部分奖励,应该就足够你赚到钱的了。

出块节点和备用节点的收益

具体的节点奖励的这1%,分为两部分:出块奖励(0.25%)和得票奖励(0.75%)。

出块奖励只是分配给出块的前21个节点,而得票奖励呢,则是用作所有节点(包括出块节点和备用节点)的奖励。

  • 出块奖励占据EOS总量的0.25%,这部分前21个节点平分。
    假设总量是10亿,那么,出块奖励部分,则是两百五十万 EOS,这部分21个节点平分。
  • 得票奖励, 占0.75%,这部分由所有节点按照投票权重占比来分配的。

例如,在某个时刻,如果未分配的得票奖励部分是一万2千个EOS,那么,假设 EOS Cannon节点的投票权重比例为10%,则佳能可以分得10%的奖励,即两千五百个EOS。

另外,佳能作为出块节点,还可以因为出块得到奖励。
假设未分配的出块奖励为两千一百个EOS,那么,佳能得到的奖励就是一百个,因为出块奖励是21个节点平分的。

总结

  • 如果某个节点是前21个节点之一,那么,其奖励分为:出块奖励部分 + 得票奖励部分;
  • 如果某个节点是备用节点,那么,它可能得到的收益属于得票奖励之中的一部分,比如总共投票权重为100,该节点所得到的投票权重为5,则可以分到得票奖励的5%。
  • 如果某个备用节点的得票奖励不足100个EOS,则无法得到奖励。

转载自:https://www.jianshu.com/p/8fb3531d055c

EOS每年增发5%,1/5作为节点奖励,剩下4/5居然都给了它?

一、EOS通胀及奖励怎么分?

众所周知,EOS采取每年增发5%代币的通胀模式。这是因为EOS采用了DPOS共识机制,而且没有任何交易手续费(明面上的),所以它通过增发代币的方式来鼓励节点提供计算资源和记账。

但实际上,这些增发的EOS并不完全用于节点奖励,而是分为两部分,其中节点奖励占五分之一,即1%的EOS增发量;剩下的归WPS提案基金(WPF),约4%。

其中节点(包括超级节点和备用节点)奖励的四分之一,即0.25%的EOS增发量为超级节点的出块奖励,剩下的0.75%为所有节点的得票率奖励。备用节点则无出块奖励,只有得票率奖励且要求在100个EOS以上,否则便不算备用节点,无法领取奖励。

而归提案基金(WPF)所有的4%的增发奖励则服务于WPS(工作者提案系统),它就是为了奖励给对社区有益的工作提案,旨在成为社区促进创新的资金来源,以帮助发展生态系统。

二、WPS新提案流程

通过上面的介绍,我们大致了解了WPS是什么。那么在WPS中,我们又该如何将提案提交公众批准呢?(内心OS:其实我就是冲着瓜分4%的增发奖励去的)

具体流程是这样的:EOS持有者发起提案,上链,经由紧急事务委员会进行紧急事务标准评估,满足标准就进行社区投票。如果投票不通过,根据反馈意见重新修改提交,如果投票通过,则获得第一阶段的基金分配

需要提醒的是,这个流程中提到的紧急事务委员会,是属于第一阶段公投时候所产生的,所处理的事务为紧急类别的事务,目前紧急类别的事务包括:Github存储库管理、安全测试、Bug赏金计划以及WPS监督委员会。

三、WPS备受争议

从WPS的设计上看,它将激励社区开发人去提供有效和可拓展的改进所需的补丁和升级,使得EOS.IO能够满足社区不断变化的需求。它无疑将填补目前EOS社区治理的部分空白,对于EOS生态发展起着一定促进作用。

虽然WPS有着诸多优点,但我们注意到这样一个问题,即4%作为提案基金是不是太高了。EOS增发的4%,这可是一笔数额不菲的资金,WPS允许将这部分资金用于由社区批准的项目,如果使用得当固然是幸事一件;但如果没用好,对EOS系统就可能会是一场打击。因此,BM也于前几周在电报群中表达了对选民投票积极性以及对WPS正常运转的忧虑,认为应该取消。

虽然BM这一观点得到了一部分社区成员的认同,但我个人认为:WPS的存在是值得肯定和支持的,但4%这个比例有点高,可以改为一年0.5-1.5%的比例进行试验,然后在试验中进行调整优化。

另外,核心WPS工作组也于上周发布了声明,表示当前核心工作组正在努力构建一套工具和一套拟议的规则和指南,以实现WPS的设计目标。同时,他们将会在9月至10月,执行第一次公投。第一次公投将会决定是否同意将100万EOS通证从eosio.saving帐户转移到新帐户eosio.wps,用做第一阶段的基金,用于紧急事务。(更多内容可查看:https://bihu.com/article/1163451

而在第一阶段之后,将会逐渐替换掉紧急基金会,而且如下图所示,会有监管、社区、基础设施等更多其他类别的提案产生。

所以,现在对WPS盖棺定论还为时尚早,不如让WPS“再飞一会”,待第一次公投后再看它,说不定它会成长为EOS生态中的另一个奇迹。
转载自:https://www.jianshu.com/p/4ad8e49910a9

eosjs简明使用手册(最新版)

eosjs简明使用手册(最新版)

作者:古千峰 2018-7-15

以下教程用于EOS正式版,已在DEV开发链和EOS1.0.5-1.0.7版本的主链上测试通过。

官方文档因为版本的原因,内容多有错误,仅供参考。

eosjs的API与http-RPC调用一致,如rpc使用get-block,则在eosjs中使用getBlock()方法。即去横岗,首字符大写

eosjs几个容易出错的地方

  • asset类型,表达方式为字符串,如:100.0000 EOS,千万注意小数点后四位,少一位都不行
  • 与链连接时需要指定chainId,可以通过cleos get info获得

1- 安装eosjs

eosjs用于对交易签名、交易等操作。
eosjs-api用于读取链上数据,只读,如果只需要读取链上数据的话,只需要使用eosjs-api

在nodejs中安装:

npm install eosjs
npm install eosjs-api

注意nodejs需要最新版,如安装过程发生错误,请用node -vnpm -v查看版本。

2- 建立eosjs与链的连接

Eos = require('eosjs')

// Optional configuration..
config = {
  keyProvider: ['PrivateKeys...'], // 配置私钥字符串
  httpEndpoint: 'http://178.62.196.196:8888', //DEV开发链url与端口
  chainId: "0b08e71a2f8caaccc2dc13244b788f5eba29462ecd5d5dea1ad8cbe9581e885a", // 通过cleos get info可以获取chainId
  mockTransactions: () => null, // 如果要广播,需要设为null
  transactionHeaders: (expireInSeconds, callback) => {
    callback(null/*error*/, headers) //手动设置交易记录头,该方法中的callback回调函数每次交易都会被调用
  },
  expireInSeconds: 60,
  broadcast: true,
  debug: false,
  sign: true,
  authorization: null // 该参数用于在多签名情况下,识别签名帐号与权限,格式如:account@permission
}

eos = Eos(config)

3- 建立eosjs-api与链的连接

如果加载了eosjs后,默认加载eosjs-api,无需单独链接eosjs-api与链

EosApi = require('eosjs-api')

// everything is optional
options = {
  httpEndpoint: 'http://178.62.196.196:8888',
  verbose: false, // API logging
  logger: { // Default logging functions
    //log: config.verbose ? console.log : '',
    error: console.error
  },
  fetchConfiguration: {}
}

eos = EosApi(options)

4- 获取帮助

不添加如何参数,可以获取该方法的使用说明,如:

eos.getBlock()

5- 获取链上最新出块信息

eos.getInfo({}).then(result => { 
    console.log(result) 
})

返回:

{ server_version: 'b195012b',
  chain_id: '0b08e71a2f8caaccc2dc13244b788f5eba29462ecd5d5dea1ad8cbe9581e885a',
  head_block_num: 209307,
  last_irreversible_block_num: 209267,
  last_irreversible_block_id: '00033173a9ccd4bdd60a92d257e9354023b0457b134797be472a236cd908bc31',
  head_block_id: '0003319ba8ddc60d80c3cd0c7a70695cfd951f51ace9a798c913384cfbae659c',
  head_block_time: '2018-07-15T01:51:07.500',
  head_block_producer: 'eoshackathon',
  virtual_block_cpu_limit: 100000000,
  virtual_block_net_limit: 1048576000,
  block_cpu_limit: 99900,
  block_net_limit: 1048576 
}

如果需要拿到某一个数据值,比如:head_block_producer,则使用:

eos.getInfo({}).then(result => { 
    console.log(result.head_block_producer) //以对象属性方式获取head_block_producer
})

6- 获取指定区块信息

eos.getBlock(200000).then(result => { console.log(result) }) //获取第200000个区块

或者:

eos.getBlock({block_num_or_id: 200000}).then(result => { console.log(result) }) //获取第200000个区块

或者:

eos.getBlock('00030d4011a6744857533a6e6d907037a94c27a2dc006b4d28125f76bed2b355').then(result => { console.log(result) }) //根据id获取区块

或者:

eos.getBlock({block_num_or_id: '00030d4011a6744857533a6e6d907037a94c27a2dc006b4d28125f76bed2b355'}).then(result => { console.log(result) }) //根据id获取区块

或者将返回值传到回调函数callback中处理:

callback = (err, res) => { err ? console.error(err) : console.log(res) }
eos.getBlock(200000, callback)

7- 获取账户余额

eos.getCurrencyBalance({ code: "eosio.token", account: "eosio", symbol: "DEV" }).then(result => console.log(result))

以上命令相当于

cleos get currency balance eosio.token eosio DEV

8- 获取某代币的信息

eos.getCurrencyStats({code: "eosio.token", symbol: "DEV"}, callback) //用上面出现的callback,下同

9- 获取账户信息

eos.getAccount({account_name: "eoshackathon"}, callback) //获取eoshackathon账户的信息

相当于:

cleos get account eoshackathon

返回:

{ account_name: 'eoshackathon',
  head_block_num: 219997,
  head_block_time: '2018-07-15T03:20:12.500',
  privileged: false,
  last_code_update: '1970-01-01T00:00:00.000',
  created: '2018-07-13T20:54:28.000',
  ram_quota: 8148,
  net_weight: 500000,
  cpu_weight: 500000,
  net_limit: { used: 145, available: 120795486, max: 120795631 },
  cpu_limit: { used: 1511, available: 11518458, max: 11519969 },
  ram_usage: 3414,
  permissions:
   [ { perm_name: 'active', parent: 'owner', required_auth: [Object] },
     { perm_name: 'owner', parent: '', required_auth: [Object] } ],
  total_resources:
   { owner: 'eoshackathon',
     net_weight: '50.0000 DEV',
     cpu_weight: '50.0000 DEV',
     ram_bytes: 8148 },
  self_delegated_bandwidth: null,
  refund_request: null,
  voter_info: null 
}

10- 获取智能合约代码

eos.getCode({ account_name: "eosio"}, callback)

获取eosio账户的所有合约代码

11- 获取智能合约ABI

eos.getAbi({ account_name: "eosio"}, callback)

12- 获取Table行数据

不成熟

13- 获取账户的Actions列表

eos.getActions({account_name: "eoshackathon", pos: 0, offset: 15}, callback) //pos和offset是指:从第pos条记录开始获取offset条Actions

14- 获取公钥对应的账户

eos.getKeyAccounts({ public_key: 公钥字符串}, callback)

如果查找到帐号,则返回[],如果该公钥没有对应帐号,则报错。
相当于:

cleos get accounts 公钥

15- 获取主账号控制的其他帐号

eos.getControlledAccounts({ controlling_account: "eoshackathon"}, callback)

16- 获取transaction交易细节

该指令有bug,慎用!

eos.getTransaction({id: "xxxx"}, callback)

17- 转账交易

首先,在链接配置config中,keyProvider: [发送方私钥]
其次,可以设置options参数如下:

options = {
    authorization: '发送方帐号@active',
    broadcast: true,
    sign: true
}
方式1:
eos.transfer('发送方帐号', '接收方帐号', '0.3000 DEV','memo')

如果需要对返回值处理:

eos.transfer('发送方帐号', '接收方帐号', '0.3000 DEV','memo', callback)

如果有options参数,则:

eos.transfer('发送方帐号', '接收方帐号', '0.3000 DEV','memo', options, callback)
方式2:

使用对象:

eos.transfer({ from: '发送方帐号', to: '接收方帐号', quantity: '0.1000 DEV', memo: '备注', callback })

如果不想广播交易,可以使用以下简便指令:

eos.transfer('发送方帐号', '接收方帐号', '0.3000 DEV','memo', false) //在最后加上false,不广播
方式3:

使用eos.transaction,构造对象执行

eos.transaction(
    {
        // ...headers,
        actions: [
            {
                account: 'eosio.token',
                name: 'transfer',
                authorization: [{
                    actor: '发送方帐号',
                    permission: 'active'
                }],
                data: {
                    from: '发送方帐号',
                    to: '接收方帐号',
                    quantity: '0.3000 DEV',
                    memo: '备注'
                }
            }
        ]
    }
    // options -- example: {broadcast: false}
)

以上命令与以下cleos相同:

cleos push action eosio.token transfer '[ "发送方帐号", "接收方帐号",  "0.3000 DEV", "备注" ]' -p 发送方帐号

18- 新建帐号

creatoraccount = "testtesttest" //主账号
newaccount = "testtest1113" //新账号
newaccount_pubkey = "EOS5LUYdLZAd3uHF5XGAeE61aTeSXWqvqxBSUq3uhqYH7kY15Drjf" //新账号的公钥

//构建transaction对象
eos.transaction(tr => {
    //新建账号
    tr.newaccount({
        creator: creatoraccount,
        name: newaccount,
        owner: newaccount_pubkey,
        active: newaccount_pubkey
    })

    //为新账号充值RAM
    tr.buyrambytes({
        payer: creatoraccount,
        receiver: newaccount,
        bytes: 8192
    })

    //为新账号抵押CPU和NET资源
    tr.delegatebw({
        from: creatoraccount,
        receiver: newaccount,
        stake_net_quantity: '1.0000 DEV',
        stake_cpu_quantity: '1.0000 DEV',
        transfer: 0
    })
})

19- 购买RAM

creatoraccount = "testtesttest" //主账号
newaccount = "testtest1113" //新账号

eos.transaction(tr => {
    tr.buyrambytes({
        payer: creatoraccount,
        receiver: newaccount,
        bytes: 8192 
    })

或者

eos.transaction(tr => {
    tr.buyram({
        payer: creatoraccount,
        receiver: newaccount,
        quant: 8 //以k为单位的内存,8k=8192字节
    })

20- 出售RAM

eos.transaction(tr => {
    tr.sellram({
        account: 'testtesttest',
        bytes: 1024 //出售1k内存
    })
})

21- 竞拍账号名

eos.transaction(tr => {
    tr.bidname ({
        bidder: "testtesttest",
        newname: "竞拍的名字",
        bid: 价格
    })
})

22- 抵押CPU和NET

eos.transaction(tr => {
    tr.delegatebw({
        from: "testtesttest",
        receiver: "testtesttest", //testtesttest账户为自己抵押
        stake_net_quantity: "1.0000 DEV",
        stake_cpu_quantity: "1.0000 DEV",
        transfer: 0
    })
})

23- 取消抵押(赎回)CPU和NET

eos.transaction(tr => {
    tr.undelegatebw({
        from: "testtesttest",
        receiver: "testtesttest",
        unstake_net_quantity: "0.1000 DEV", //赎回0.1个DEV
        unstake_cpu_quantity: "0.1000 DEV"
    })
})

24- 智能合约部署

如果是加载wasm合约,不用使用binaryen,如果加载wast合约,需要安装并使用binaryen,如下:

npm install binaryen@39.0.0

并用以下方式导入到js

binaryen = require('binaryen')
eos = Eos({keyProvider, binaryen})
部署合约

以官方自带的hello合约为例

fs = require('fs')
wasm = fs.readFileSync(`contracts/hello/hello.wasm`) //这个文件在客户端上,而不是在服务器上
abi = fs.readFileSync(`contracts/hello/hello.abi`)

eos.setcode('contract_name', 0, 0, wasm) // contract_name 为合约名
eos.setabi('contract_name', JSON.parse(abi)) // @returns {Promise}

25- 智能合约的执行

方法一:
eos.contract("contract_name").then(hello => {  //hello随便起的变量名
    hello.hi('axay', {                         //hi是方法名, 'axay'是该hello合约hi方法的参数
        authorization: ['testtesttest']        //testtesttest是建立该合约的用户
    }).then(result => {
        console.log(result);
    });
});
方法二:
eos.transaction(
    {
        actions: [
            {
                account: 'contract_name',  //合约名
                name: 'hi',               //方法名,该方法在官方的hello合约中有
                authorization: [{
                    actor: 'testtesttest',
                    permission: 'active'
                }],
                data: {
                    user: 'axay'
                }
            }
        ]
    }
    // options -- example: {broadcast: false}
).then(result => console.log(result))

26- 通过eosjs发行一个代币

发行代币有两种方式,一种是通过cleos参考这里,但前提是必须要安装好EOS系统。
另一种方式是通过eosjs,无需在本机安装EOS系统。

注意:

此操作需要以eosio.token账户进行操作,因此需将eosio.token帐号的私钥导入到keyProvider数组中。但如果出于安全原因,不允许将eosio.token账户私钥加到程序中的话,则可以由发币的用户先部署eosio.token合约,然后再做接下去的操作。

第一步:创建代币

eos.transaction(
    {
        // ...headers,
        actions: [
            {
                account: 'eosio.token',       //合约名
                name: 'create',               //调用创建代币的方法
                authorization: [{
                    actor: 'eosio.token',     //必须是eosio.token
                    permission: 'active'
                }],
                data: {
                    issuer: 'testtesttest',   //代币发行方
                    maximum_supply: '10000.0000 AAA', //代币总量与名称
                    can_freeze: 0,
                    can_recall: 0,
                    can_whitelist: 0
                }
            }
        ]
    }
    // options -- example: {broadcast: false}
).then(result => console.log(result))

第二步:发行代币

eos.transaction(
    {
        // ...headers,
        actions: [
            {
                account: 'eosio.token',      //合约名
                name: 'issue',               //调用发行代币的方法
                authorization: [{
                    actor: 'testtesttest',   //必须是代币的发行方
                    permission: 'active'
                }],
                data: {
                    to: 'testtesttest',      //收到代币的帐号
                    quantity: '1000.0000 AAA',
                    memo: "testtesttest issue 1000 AAA"
                }
            }
        ]
    }
    // options -- example: {broadcast: false}
).then(result => console.log(result))

也可以将以上两步合在一起操作,如:

eos.transaction(
    {
        // ...headers,
        actions: [
            {
                account: 'eosio.token',  //合约
                name: 'create',               //方法
                authorization: [{
                    actor: 'eosio.token',
                    permission: 'active'
                }],
                data: {
                    issuer: 'testtesttest',
                    maximum_supply: '10000000.0000 GAT',
                    can_freeze: 0,
                    can_recall: 0,
                    can_whitelist: 0
                }
            },
            {
                account: 'eosio.token',  //合约
                name: 'issue',               //方法
                authorization: [{
                    actor: 'testtesttest',
                    permission: 'active'
                }],
                data: {
                    to: 'testtest1111',
                    quantity: '1000.0000 GAT',
                    memo: "testtesttest issue 1000 GAT to testtest1111"
                }
            }
        ]
    }
    // options -- example: {broadcast: false}
).then(result => console.log(result))

https://github.com/eoshackathon/eos_dapp_development_cn/edit/master/docs/eosjs_manual.md

EOS 智能合约最佳安全开发指南

EOS 智能合约最佳安全开发指南

这篇文档旨在为 EOS 智能合约开发人员提供一些智能合约的安全准则已知漏洞分析。我们邀请社区对该文档提出修改或完善建议,欢迎各种合并请求(Pull Request)。若有相关的文章或博客的发表,也请将其加入到参考文献中。

目录

安全准则

EOS 处于早期阶段并且有很强的实验性质。因此,随着新的 bug 和安全漏洞被发现,新的功能不断被开发出来,其面临的安全威胁也是不断变化的。这篇文章对于开发人员编写安全的智能合约来说只是个开始。

开发智能合约需要一个全新的工程思维,它不同于我们以往项目的开发。因为它犯错的代价是巨大的,很难像中心化类型的软件那样,打上补丁就可以弥补损失。就像直接给硬件编程或金融服务类软件开发,相比于 Web 开发和移动开发都有更大的挑战。因此,仅仅防范已知的漏洞是不够的,还需要学习新的开发理念:

  • 对可能的错误有所准备。任何有意义的智能合约或多或少都存在错误,因此你的代码必须能够正确的处理出现的 bug 和漏洞。需始终保证以下规则:
    • 当智能合约出现错误时,停止合约
    • 管理账户的资金风险,如限制(转账)速率、最大(转账)额度
    • 有效的途径来进行 bug 修复和功能提升
  • 谨慎发布智能合约。 尽量在正式发布智能合约之前发现并修复可能的 bug。
    • 对智能合约进行彻底的测试,并在任何新的攻击手法被发现后及时的测试(包括已经发布的合约)
    • 从 alpha 版本在麒麟测试网(CryptoKylin-Testnet)上发布开始便邀请专业安全审计机构进行审计,并提供漏洞赏金计划(Bug Bounty)
    • 阶段性发布,每个阶段都提供足够的测试
  • 保持智能合约的简洁。复杂会增加出错的风险。
    • 确保智能合约逻辑简洁
    • 确保合约和函数模块化
    • 使用已经被广泛使用的合约或工具(比如,不要自己写一个随机数生成器)
    • 条件允许的话,清晰明了比性能更重要
    • 只在你系统的去中心化部分使用区块链
  • 保持更新。通过公开资源来确保获取到最新的安全进展。
    • 在任何新的漏洞被发现时检查你的智能合约
    • 尽可能快的将使用到的库或者工具更新到最新
    • 使用最新的安全技术
  • 清楚区块链的特性。尽管你先前所拥有的编程经验同样适用于智能合约开发,但这里仍然有些陷阱你需要留意:
    • require_recipient(account_name name) 可触发通知,调用name合约中的同名函数,官方文档

已知漏洞

数值溢出

在进行算术运算时,未进行边界检查可能导致数值上下溢,引起智能合约用户资产受损。

漏洞示例

存在缺陷的代码:batchTransfer 批量转账

typedef struct acnts {
    account_name name0;
    account_name name1;
    account_name name2;
    account_name name3;
} account_names;

void transfer(symbol_name symbol, account_name from, account_names to, uint64_t balance)
{
    require_auth(from);
    account fromaccount;

    require_recipient(from);
    require_recipient(to.name0);
    require_recipient(to.name1);
    require_recipient(to.name2);
    require_recipient(to.name3);

    eosio_assert(is_balance_within_range(balance), "invalid balance");
    eosio_assert(balance > 0, "must transfer positive balance");

    uint64_t amount = balance * 4; //乘法溢出

    int itr = db_find_i64(_self, symbol, N(table), from);
    eosio_assert(itr >= 0, "Sub-- wrong name");
    db_get_i64(itr, &fromaccount, (account));
    eosio_assert(fromaccount.balance >= amount, "overdrawn balance");

    sub_balance(symbol, from, amount);

    add_balance(symbol, to.name0, balance);
    add_balance(symbol, to.name1, balance);
    add_balance(symbol, to.name2, balance);
    add_balance(symbol, to.name3, balance);
}

防御方法

尽可能使用 asset 结构体进行运算,而不是把 balance 提取出来进行运算。

真实案例

权限校验

在进行相关操作时,应严格判断函数入参和实际调用者是否一致,使用require_auth进行校验。

漏洞示例

存在缺陷的代码:transfer 转账

void token::transfer( account_name from,
                      account_name to,
                      asset        quantity,
                      string       memo )
{
    eosio_assert( from != to, "cannot transfer to self" );
    eosio_assert( is_account( to ), "to account does not exist");
    auto sym = quantity.symbol.name();
    stats statstable( _self, sym );
    const auto& st = statstable.get( sym );

    require_recipient( from );
    require_recipient( to );

    eosio_assert( quantity.is_valid(), "invalid quantity" );
    eosio_assert( quantity.amount > 0, "must transfer positive quantity" );
    eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
    eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" );

    auto payer = has_auth( to ) ? to : from;

    sub_balance( from, quantity );
    add_balance( to, quantity, payer );
}

防御方法

使用require_auth( from )校验资产转出账户与调用账户是否一致。

真实案例

暂无

apply 校验

在处理合约调用时,应确保每个 action 与 code 均满足关联要求。

漏洞示例

存在缺陷的代码:

// extend from EOSIO_ABI
#define EOSIO_ABI_EX( TYPE, MEMBERS ) \
extern "C" { \
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
      auto self = receiver; \
      if( action == N(onerror)) { \
         /* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
         eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
      } \
      if( code == self || code == N(eosio.token) || action == N(onerror) ) { \
         TYPE thiscontract( self ); \
         switch( action ) { \
            EOSIO_API( TYPE, MEMBERS ) \
         } \
         /* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
      } \
   } \
}

EOSIO_ABI_EX(eosio::charity, (hi)(transfer))

防御方法

使用

if( ((code == self  && action != N(transfer) ) || (code == N(eosio.token) && action == N(transfer)) || action == N(onerror)) ) { }

绑定每个关键 action 与 code 是否满足要求,避免异常调用。

真实案例

参考文献

致谢

  • 麒麟工作组
  • eosiofans
  • 荆凯(EOS42)
  • 星魂
  • 岛娘
  • 赵余(EOSLaoMao)
  • 字符

以传统的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.创建麒麟测试网的测试账号,用于合约的部署,以及使用资源的购买

  • 创建账号:http://faucet.cryptokylin.io/create_account?new_account_name
    比如我们要创建的账号为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"
    }
  • 查询账户余额 https://tools.cryptokylin.io/#/tx/

申请来的测试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使用,一整套简单的流程。