数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

【围观】麒麟芯片遭打压成绝版,华为亿元投入又砸向了哪里?>>>
Hyperledger Fabric(HF)为终端用户使用自有CA提供了fabric-ca工具。然而在生产环境中应当尽可能保证根CA的安全性,例如让根CA离线,而将Hyperledger Fabric环境中的证书签发代理给中间 CA。在本文中,我们将介绍如何使用第三方CA作为根CA,使用fabric-ca作为中间CA,以及如何在CA信任链中整合第三方CA与fabric-ca。 Hyperledger Fabric区块链开发教程: Node.js | Java | Golang
1、准备工作
为了演示外部CA和中间CA的使用,我们将部署一个根CA,它只负责签发中间CA的证书。这个中间CA采用Fabric CA,负责签发用户和节点证书。出于简化考虑,在这个教程中,我们将使OpenSSL。
在开始之前,请参考相应文档先完成以下前置环节的部署和知识准备: 安装docker、docker-compose、git和curl Hyperledger Fabric和bash/sheel脚本基础知识 PKI基础知识
克隆本教程演示代码仓库,其中包含了所有用到的脚本: git clone https://github.com/aldredb/external-ca cd external-ca
下载Hyperledger Fabric预编译程序并删除不需要的文件。我们只用到cryptogen, fabric-ca-client和configtx。 curl -sSL http://bit.ly/2ysbOFE | bash -s -- 1.4.1 -d -s rm -f config/configtx.yaml config/core.yaml config/orderer.yaml
使用cryptogen为排序节点机构生成证书和密钥: export PATH=$PWD/bin:$PATH export FABRIC_CFG_PATH=${PWD} cryptogen generate --config=./crypto-config.yaml
创建用于保存对等节点机构 org1.example.com 的证书和密钥的目录结构。如下所示,该机构包含一个节点 peer0.org1.example.com 和两个用户: admin 和 Admin@org1.example.com 。中间CA的证书和密钥将保存在ca文件夹里。 ORG_DIR=$PWD/crypto-config/peerOrganizations/org1.example.com PEER_DIR=$ORG_DIR/peers/peer0.org1.example.com REGISTRAR_DIR=$ORG_DIR/users/admin ADMIN_DIR=$ORG_DIR/users/Admin@org1.example.com mkdir -p $ORG_DIR/ca $ORG_DIR/msp $PEER_DIR $REGISTRAR_DIR $ADMIN_DIR
2、创建中间CA
生成私钥和证书签名请求/CSR。注意机构的值与根CA相同。 openssl ecparam -name prime256v1 -genkey -noout -out \ $ORG_DIR/ca/ica.org1.example.com.key.pem openssl req -new -sha256 -key $ORG_DIR/ca/ica.org1.example.com.key.pem \ -out $ORG_DIR/ca/ica.org1.example.com.csr \ -subj "/C=SG/ST=Singapore/L=Singapore/O=org1.example.com/OU=/CN=ica.org1.example.com"
根CA负责签名中间CA的CSR并签发证书,中间CA证书的有效期是根CA证书的一半。注意我们使用v3_intermediate_ca扩展。 openssl ca -batch -config openssl_root.cnf -extensions v3_intermediate_ca \ -days 1825 -notext -md sha256 -in $ORG_DIR/ca/ica.org1.example.com.csr \ -out $ORG_DIR/ca/ica.org1.example.com.crt.pem
现在让我们看一下得到的证书: openssl x509 -in $ORG_DIR/ca/ica.org1.example.com.crt.pem -text -noout
如下所示,可以看到证书的签发机构是: rca.org1.example.com: Issuer: C=SG, ST=Singapore, L=Singapore, O=org1.example.com, CN=rca.org1.example.com Validity Not Before: May 3 10:16:44 2019 GMT Not After : Apr 30 10:16:44 2029 GMT Subject: C=SG, ST=Singapore, O=org1.example.com, CN=ica.org1.example.com
一旦我们签发了中间CA的证书,就不再需要根CA了,除非需要创建另一个中间CA或者回收中间CA的证书。
现在创建CA链文件,其中包含了中间CA和genCA的证书: cat $ORG_DIR/ca/ica.org1.example.com.crt.pem \ $PWD/rca/certs/rca.org1.example.com.crt.pem > \ $ORG_DIR/ca/chain.org1.example.com.crt.pem
最后启动中间CA,中间CA的配置文件指向我们前面创建的证书、密钥和CA链。 你可以参考ca-config/fabric-ca-server-config.yaml文件: docker-compose up -d ica.org1.example.com
3、签发peer节点证书和用户证书
中间CA就绪后,我们现在可以签发用户证书和peer节点证书了。
首先加入(enroll)admin用户,该用户有权限注册(register)其他用户: export FABRIC_CA_CLIENT_HOME=$REGISTRAR_DIR fabric-ca-client enroll --csr.names C=SG,ST=Singapore,L=Singapore,O=org1.example.com \ -m admin -u http://admin:adminpw@localhost:7054
现在用admin注册机构 org1.example.com 的管理员 Admin@org1.example.com ,以及peer节点 peer0.org1.example.com : fabric-ca-client register --id.name Admin@org1.example.com \ --id.secret mysecret --id.type client --id.affiliation org1 \ -u http://localhost:7054fabric-ca-client register \ --id.name peer0.org1.example.com --id.secret mysecret \ --id.type peer --id.affiliation org1 -u http://localhost:7054
加入 Admin@org1.example.com : export FABRIC_CA_CLIENT_HOME=$ADMIN_DIR fabric-ca-client enroll \ --csr.names C=SG,ST=Singapore,L=Singapore,O=org1.example.com \ -m Admin@org1.example.com \ -u http://Admin@org1.example.com:mysecret@localhost:7054 mkdir -p $ADMIN_DIR/msp/admincerts && \ cp $ADMIN_DIR/msp/signcerts/*.pem $ADMIN_DIR/msp/admincerts/
加入 peer0.org1.example.com : export FABRIC_CA_CLIENT_HOME=$PEER_DIRfabric-ca-client enroll \ --csr.names C=SG,ST=Singapore,L=Singapore,O=org1.example.com \ -m peer0.org1.example.com \ -u http://peer0.org1.example.com:mysecret@localhost:7054 mkdir -p $PEER_DIR/msp/admincerts && \ cp $ADMIN_DIR/msp/signcerts/*.pem $PEER_DIR/msp/admincerts/
现在让我们看一下其中某个证书,例如 peer0.org1.example.com 的证书: openssl x509 -in $PEER_DIR/msp/signcerts/cert.pem -text -noout
证书的签发者应当是 ica.org1.example.com : Issuer: C=SG, ST=Singapore, O=org1.example.com, CN=ica.org1.example.com Validity Not Before: May 3 10:27:59 2019 GMT Not After : May 2 10:28:00 2020 GMT Subject: C=SG, ST=Singapore, L=Singapore, O=org1.example.com, OU=peer, OU=org1, CN=peer0.org1.example.com
恭喜!我们已经完成了第三方CA和Fabric CA的整合!
原文链接: Hyperledger Fabric中使用第三方CA的教程及源码 — 汇智网
区块链
2020-02-23 10:06:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
好多朋友第一次接触以太坊的时候,都会搞不清什么是Gas,更不理解Gas Price和Gas Limit的作用是什么。 本文将逐一介绍并理清这三者之间的关系,相信你看完后就会理解以太坊中这三个gas相关的概念了。 如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 去中心化电商DApp实战开发
Gas
Gas对应于一个交易(Transaction)中以太坊虚拟机(EVM)的实际运算步数。 越简单的交易,例如单纯的 以太币转帐交易,需要的运算步数越少, Gas亦会需要的少一点。 反之,如果要计算一些复杂运算,Gas的消耗 量就会大。 所以你提交的交易需要EVM进行的计算量越大,所需的Gas消耗量就越高了。
Gas Price
Gas Price就是你愿意为一个单位的Gas出多少Eth,一般用Gwei作单位。 所以Gas Price 越高, 就表示交易中每运算一步,会支付更多的Eth。
大家可能对Gwei 这个单位感到陌生,Gwei 其实就是10 ^ -9 Eth,也就是说1 Gwei = 0.000000001 Eth。 所以,当你设定Gas price = 20 Gwei ,就意味着你愿意为单步运算支付0.00000002 Eth。
说到这里,聪明如你就会意识到以太坊的手续费计算公式很简单: 交易手续费(Tx Fee) = 实际运行步数(Actual Gas Used) * 单步价格(Gas Price)
例如你的交易需要以太坊执行50步完成运算,假设你设定的Gas Price是2 Gwei ,那么整个交易的手续费 就是 50 * 2 = 100 Gwei 了。
Gas Limit
Gas Limit就是一次交易中Gas的可用上限,也就是你的交易中最多会执行多少步运算。 由于交易复杂程度各有不同, 确切的Gas消耗量是在完成交易后才会知道,因此在你提交交易之前,需要为交易设定一个Gas用量的上限。
如果说你提交的交易尚未完成,消耗的Gas就已经超过你设定的Gas Limit,那么这次交易就会被取消,而 已经消耗的手续费同样被扣取 —— 因为要奖励已经付出劳动的矿工。 而如果交易已经完成,消耗的Gas未达到Gas Limit, 那么只会按实际消耗的Gas 收取交易服务费。 换句话说,一个交易可能被收取的最高服务费就是Gas Limit * Gas​​ Price 了。
最后值得一提的是Gas Price 越高,你提交的交易会越快被矿工接纳。 但通常人们都不愿多支付手续费, 那么究竟应该将Gas Price设置为多少,才可以在正常时间(eg 10 mins)内,确保交易被确认到区域链上呢? 这个网站 可以帮到你。 写这篇文章时候,1 Gwei的Gas Price 就可以确保 交易在50 秒左右被接纳。
原文: 1分钟搞清Gas/ Gas Price/ Gas Limit
区块链
2018-05-04 18:49:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1、背景介绍
上一节讲了使用NodeJs创建投票DApp应用,可见:
以太坊区块链使用NodeJs、Web3开发投票DApp过程示例
主要讲了使用NodeJs创建投票DApp应用,是非常基础的一些操作,包括编译和部署都是使用web3最基础的方法,这有助于加深对基础知识的了解,现在对此实例进行升级,使用Truffle开发框架,并添加通证进行改造,改造后的投票DApp功能主要为:每个投票者需要先使用以太币购买投票通证,购买的越多则可以投票的数量也就越多,相当于股票 所拥有的股票越多,则在董事会的投票权也就越多。
提供网页操作,可以查看自己当前对每个人的投票数量,已经自己剩余的投票数,开发完成后效果预览如下:

2、环境准备
准备开发前需要准备如下工作 本地环境安装最新版本NodeJS 熟悉Truffle框架的基本操作 本地环境安装Ganache模拟节点环境 熟悉web3常见API
新建目录 Voting-Truffle-Token 作为工作目录。
在此目录下使用 Truffle 初始化 webpack模板,在contracts 目录下删除原有的 ConvertLib.sol、MetaCoin.sol两个文件。
3、智能合约编写
在contracts目录中新建 Voting.sol 合约文件,并在Remix环境中进行编写,编写完成后内容如下: pragma solidity ^0.4.18; // 使用通证改造后的投票DApp // 2018/05/04 // Ruoli contract Voting { //------------------------------------------------------------------------- //存储每个投票人的信息 struct voter { address voterAddress; //投票人账户地址 uint tokensBought;//投票人持有的投票通证数量 uint[] tokensUsedPerCandidate;//为每个候选人消耗的股票通证数量 } //投票人信息 mapping (address => voter) public voterInfo; //------------------------------------------------------------------------- //每个候选人获得的投票 mapping (bytes32 => uint) public votesReceived; //候选人名单 bytes32[] public candidateList; //发行的投票通证总量 uint public totalTokens; //投票通证剩余数量 uint public balanceTokens; //投票通证单价 uint public tokenPrice; //构造方法,合约部署时执行一次, 初始化投票通证总数量、通证单价、所有候选人信息 constructor(uint tokens, uint pricePerToken, bytes32[] candidateNames) public { candidateList = candidateNames; totalTokens = tokens; balanceTokens = tokens; tokenPrice = pricePerToken; } //购买投票通证,此方法使用 payable 修饰,在Sodility合约中, //只有声明为payable的方法, 才可以接收支付的货币(msg.value值) function buy() payable public returns (uint) { uint tokensToBuy = msg.value / tokenPrice; //根据购买金额和通证单价,计算出购买量 require(tokensToBuy <= balanceTokens); //继续执行合约需要确认合约的通证余额不小于购买量 voterInfo[msg.sender].voterAddress = msg.sender; //保存购买人地址 voterInfo[msg.sender].tokensBought += tokensToBuy; //更新购买人持股数量 balanceTokens -= tokensToBuy; //将售出的通证数量从合约的余额中剔除 return tokensToBuy; //返回本次购买的通证数量 } //获取候选人获得的票数 function totalVotesFor(bytes32 candidate) view public returns (uint) { return votesReceived[candidate]; } //为候选人投票,并使用一定数量的通证表示其支持力度 function voteForCandidate(bytes32 candidate, uint votesInTokens) public { //判断被投票候选人是否存在 uint index = indexOfCandidate(candidate); require(index != uint(-1)); //初始化 tokensUsedPerCandidate if (voterInfo[msg.sender].tokensUsedPerCandidate.length == 0) { for(uint i = 0; i < candidateList.length; i++) { voterInfo[msg.sender].tokensUsedPerCandidate.push(0); } } //验证投票人的余额是否足够(购买总额-已花费总额>0) uint availableTokens = voterInfo[msg.sender].tokensBought - totalTokensUsed(voterInfo[msg.sender].tokensUsedPerCandidate); require (availableTokens >= votesInTokens); votesReceived[candidate] += votesInTokens; voterInfo[msg.sender].tokensUsedPerCandidate[index] += votesInTokens; } // 计算 投票人总共花费了多少 投票通证 function totalTokensUsed(uint[] _tokensUsedPerCandidate) private pure returns (uint) { uint totalUsedTokens = 0; for(uint i = 0; i < _tokensUsedPerCandidate.length; i++) { totalUsedTokens += _tokensUsedPerCandidate[i]; } return totalUsedTokens; } //获取候选人的下标 function indexOfCandidate(bytes32 candidate) view public returns (uint) { for(uint i = 0; i < candidateList.length; i++) { if (candidateList[i] == candidate) { return i; } } return uint(-1); } //方法声明中的 view 修饰符,这表明该方法是只读的,即方法的执行 //并不会改变区块链的状态,因此执行这些交易不会耗费任何gas function tokensSold() view public returns (uint) { return totalTokens - balanceTokens; } function voterDetails(address user) view public returns (uint, uint[]) { return (voterInfo[user].tokensBought, voterInfo[user].tokensUsedPerCandidate); } //将合约里的资金转移到指定账户 function transferTo(address account) public { account.transfer(this.balance); } function allCandidates() view public returns (bytes32[]) { return candidateList; } }
修改migrations/2_deploy_contracts.js 文件,内容如下: var Voting = artifacts.require("./Voting.sol"); module.exports = function(deployer) { //初始化合约,提供10000个投票通证,每隔通证单价 0.01 ether,候选人为 'Rama', 'Nick', 'Jose' deployer.deploy(Voting,10000, web3.toWei('0.01', 'ether'), ['Rama', 'Nick', 'Jose']); };
至此合约的编写完成。

4、智能合约编译
执行 truffle compile 命令进行编译操作,如下: PS C:\Workspace\Ruoli-Code\Voting-Truffle-Token> truffle compile Compiling .\contracts\Migrations.sol... Compiling .\contracts\Voting.sol... Compilation warnings encountered: /C/Workspace/Ruoli-Code/Voting-Truffle-Token/contracts/Migrations.sol:11:3: Warning: Defining constructors as functions with the same name as the contract is deprecated. Use "constructor(...) { ... }" instead. function Migrations() public { ^ (Relevant source part starts here and spans across multiple lines). ,/C/Workspace/Ruoli-Code/Voting-Truffle-Token/contracts/Voting.sol:104:22: Warning: Using contract member "balance" inherited from the address type is deprecated. Convert the contract to "address" type to access the member, for example use "address(contract).balance" instead. account.transfer(this.balance); ^----------^ Writing artifacts to .\build\contracts
没有提示错误,编译成功,在当前目录下出现了build目录。
5、合约的部署与测试
部署前需要先启动 Ganache模拟节点,并且修改 truffle.js 文件,内容如下: // Allows us to use ES6 in our migrations and tests. require('babel-register') module.exports = { networks: { development: { host: '127.0.0.1', port: 7545, network_id: '*' // Match any network id } } }
执行truffle deploy 进行部署操作,如下: PS C:\Workspace\Ruoli-Code\Voting-Truffle-Token> truffle deploy Using network 'development'. Running migration: 1_initial_migration.js Deploying Migrations... ... 0x6b327c157804151269c5db193507a51a2cff40f64f81bd39ee3bcc567e6d93ce Migrations: 0xb81237dd01159a36a5ac3c760d227bbafe3341ea Saving successful migration to network... ... 0xc5be542ec02f5513ec21e441c54bd31f0c86221da26ed518a2da25c190faa24b Saving artifacts... Running migration: 2_deploy_contracts.js Deploying Voting... ... 0xf836862d3fccbbd971ea61cca1bb41fe25f4665b80ac6c2498396cfeb1633141 Voting: 0x6ba286f3115994baf1fed1159e81f77c9e1cd4fa Saving successful migration to network... ... 0xc8d5533c11181c87e6b60d4863cdebb450a2404134aea03a573ce6886905a00b Saving artifacts... PS C:\Workspace\Ruoli-Code\Voting-Truffle-Token>
查看Ganache中第一个账户的以太币余额略有减少,说明部署成功,下面编写测试代码对合约进行测试,在test目录先删除原有的所有文件,新建 TestVoting.js 文件,内容如下: var Voting = artifacts.require("./Voting.sol"); contract('Voting',(accounts) => { it("投票合约应该有10000个预售投票通证", function() { return Voting.deployed().then(function(instance) { return instance.totalTokens.call(); }).then((balance)=>{ assert.equal(balance.valueOf(), 10000, "10000个预售投票通证 不符合预期 :"+balance.valueOf()); }); }); it("投票合约已经售出的投票通证应该为0", function() { return Voting.deployed().then(function(instance) { return instance.tokensSold.call(); }).then((balance)=>{ assert.equal(balance.valueOf(), 0, "投票合约已经售出的投票通证数量 不符合预期 :"+balance.valueOf()); }); }); it("购买 100个通证,总价值 1 ether ", function() { return Voting.deployed().then(function(instance) { return instance.buy.call({value:web3.toWei('1', 'ether')}); }).then((balance)=>{ assert.equal(balance.valueOf(), 100, "购买100个通证 不符合预期 :"+balance.valueOf()); }); }); it("投票合约已经售出的投票通证应该为100", function() { return Voting.deployed().then(function(instance) { return instance.tokensSold.call(); }).then((balance)=>{ assert.equal(balance.valueOf(), 100, "投票合约已经售出的投票通证应该为100 不符合预期 :"+balance.valueOf()); }); }); });
在根目录执行 truffle test 即可针对此单元测试文件进行测试,如下图: PS C:\Workspace\Ruoli-Code\Voting-Truffle-Token> truffle test Using network 'development'. Contract: Voting √ 投票合约应该有10000个预售投票通证 √ 投票合约已经售出的投票通证应该为0 √ 购买 100个通证,总价值 1 ether 1) 投票合约已经售出的投票通证应该为100 > No events were emitted 3 passing (161ms) 1 failing 1) Contract: Voting 投票合约已经售出的投票通证应该为100: AssertionError: 投票合约已经售出的投票通证应该为100 不符合预期 :0: expected '0' to equal 100 at C:/Workspace/Ruoli-Code/Voting-Truffle-Token/test/TestVoting.js:33:14 at at process._tickCallback (internal/process/next_tick.js:188:7)
至此,测试完成。
6、前端网页编写
在app目录新建 index.html ,内容如下: Decentralized Voting App

候选人信息

姓名 得票数

通证信息

通证项
当前在售通证
已售出通证
通证单价
合约账户余额

参与投票



发起投票

购买投票通证

确认购买

查看投票人信息

查 看

在 app/javascripts 目录下新建 app.js ,内容如下: // Import the page's CSS. Webpack will know what to do with it. //import "../stylesheets/app.css"; // Import libraries we need. import { default as Web3} from 'web3'; import { default as contract } from 'truffle-contract' import voting_artifacts from '../../build/contracts/Voting.json' let Voting = contract(voting_artifacts); let candidates = {} let tokenPrice = null; function populateCandidates() { Voting.deployed().then((contractInstance) => { //查询所有候选人 contractInstance.allCandidates.call().then((candidateArray) => { for(let i=0; i < candidateArray.length; i++) { candidates[web3.toUtf8(candidateArray[i])] = "candidate-" + i; } setupCandidateRows(); populateCandidateVotes(); populateTokenData(); }); }); } function populateCandidateVotes() { let candidateNames = Object.keys(candidates); for (var i = 0; i < candidateNames.length; i++) { let name = candidateNames[i]; Voting.deployed().then(function(contractInstance) { contractInstance.totalVotesFor.call(name).then(function(v) { $("#" + candidates[name]).html(v.toString()); }); }); } } function setupCandidateRows() { Object.keys(candidates).forEach( (candidate) => { $("#candidate-rows").append("" + candidate + ""); }); } function populateTokenData() { Voting.deployed().then(function(contractInstance) { contractInstance.totalTokens().then(function(v) { $("#tokens-total").html(v.toString()); }); contractInstance.tokensSold.call().then(function(v) { $("#tokens-sold").html(v.toString()); }); contractInstance.tokenPrice().then(function(v) { tokenPrice = parseFloat(web3.fromWei(v.toString())); $("#token-cost").html(tokenPrice + " Ether"); }); web3.eth.getBalance(contractInstance.address, function(error, result) { $("#contract-balance").html(web3.fromWei(result.toString()) + " Ether"); }); }); } //初始化加载 $( document ).ready(function() { if (typeof web3 !== 'undefined') { console.warn("Using web3 detected from external source like Metamask") // Use Mist/MetaMask's provider window.web3 = new Web3(web3.currentProvider); } else { window.web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545")); } Voting.setProvider(web3.currentProvider); populateCandidates(); //初始化查看投票人事件 $("#voter-lookup-btn").click(() => { let address = $("#voter-info").val(); Voting.deployed().then((contractInstance) => { //获取投票人信息 contractInstance.voterDetails.call(address).then( (v) => { $("#tokens-bought").html("
总共购买投票通证数量: " + v[0].toString()); let votesPerCandidate = v[1]; $("#votes-cast").empty(); $("#votes-cast").append("通证已经用于投票记录如下:
"); let table_data=""; let allCandidates = Object.keys(candidates); for(let i=0; i < allCandidates.length; i++) { table_data+=""; } table_data+="
"+allCandidates[i]+""+votesPerCandidate[i]+"
"; $("#votes-cast").append(table_data); }); }); }); //发起投票操作事件 $("#voter-send").click(() => { let candidateName = $("#candidate").val(); //获取被投票的候选人 let voteTokens = $("#vote-tokens").val(); //获取票数 $("#candidate").val(""); $("#vote-tokens").val(""); Voting.deployed().then( (contractInstance) => { contractInstance.voteForCandidate(candidateName, voteTokens, {gas: 140000, from: web3.eth.accounts[1]}).then( () => { let div_id = candidates[candidateName]; return contractInstance.totalVotesFor.call(candidateName).then( (v) => { //更新候选人票数 $("#" + div_id).html(v.toString()); $("#msg").fadeIn(300); setTimeout(() => $("#msg").fadeOut(1000),1000); }); }); }); }); //绑定购买通证事件 $("#voter_buyTokens").click(() => { let tokensToBuy = $("#buy").val(); let price = tokensToBuy * tokenPrice; Voting.deployed().then(function(contractInstance) { contractInstance.buy({value: web3.toWei(price, 'ether'), from: web3.eth.accounts[1]}).then(function(v) { web3.eth.getBalance(contractInstance.address, function(error, result) { $("#contract-balance").html(web3.fromWei(result.toString()) + " Ether"); }); $("#buy-msg").fadeIn(300); setTimeout(() => $("#buy-msg").fadeOut(1000),1000); }) }); populateTokenData(); }); });
添加完成这连个文件,前端页面开发完成

7、页面测试
在根目录输入 npm run dev 启动此工程,如下: > truffle-init-webpack@0.0.2 dev C:\Workspace\Ruoli-Code\Voting-Truffle-Token > webpack-dev-server Project is running at http://localhost:8081/ webpack output is served from / Hash: 311e234883b64483e595 Version: webpack 2.7.0 Time: 1322ms Asset Size Chunks Chunk Names app.js 1.79 MB 0 [emitted] [big] main index.html 3.5 kB [emitted] chunk {0} app.js (main) 1.77 MB [entry] [rendered] [71] ./app/javascripts/app.js 4.68 kB {0} [built] [72] (webpack)-dev-server/client?http://localhost:8081 7.93 kB {0} [built] [73] ./build/contracts/Voting.json 163 kB {0} [built] [109] ./~/loglevel/lib/loglevel.js 7.86 kB {0} [built] [117] ./~/strip-ansi/index.js 161 bytes {0} [built] [154] ./~/truffle-contract-schema/index.js 5.4 kB {0} [built] [159] ./~/truffle-contract/index.js 2.64 kB {0} [built] [193] ./~/url/url.js 23.3 kB {0} [built] [194] ./~/url/util.js 314 bytes {0} [built] [195] ./~/web3/index.js 193 bytes {0} [built] [229] (webpack)-dev-server/client/overlay.js 3.67 kB {0} [built] [230] (webpack)-dev-server/client/socket.js 1.08 kB {0} [built] [231] (webpack)/hot nonrecursive ^\.\/log$ 160 bytes {0} [built] [232] (webpack)/hot/emitter.js 77 bytes {0} [built] [233] multi (webpack)-dev-server/client?http://localhost:8081 ./app/javascripts/app.js 40 bytes {0} [built] + 219 hidden modules webpack: Compiled successfully.

启动完成后,在浏览器中访问 http://localhost:8081/ ,即可看到页面最开始展示的效果,可以用于购买通证,发起投票以及查看每个账户的投票记录信息。
由于是使用Ganache中第一个账户进行部署的合约,上述代码中是使用 Ganache第二个账户进行购买通证及发起投票的,所以在打开Ganache主页,即可发现由于购买通证,第二个账户的以太币已经减少,但为什么减少的以太币没有转入第一个账户,这个需要进行一下合约账户余额转出操作,对应合约中的 transferTo 方法,此处没有调用。
区块链
2018-05-04 16:57:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Hyperledger Fabric 超级账本的硬伤
Hyperledger Fabric 超级账本的硬伤
Netkiller Blockchain 手札
本文作者最近在找工作,有意向致电 13113668890
Mr. Neo Chan, 陈景峯(BG7NYT)
中国广东省深圳市龙华新区民治街道溪山美地 518131 +86 13113668890
文档始创于2018-02-10
版权 © 2018 Netkiller(Neo Chan). All rights reserved.
版权声明
转载请与作者联系,转载时请务必标明文章原始出处和作者信息及本声明。
http://www.netkiller.cn
http://netkiller.github.io
http://netkiller.sourceforge.net
微信订阅号 netkiller-ebook (微信扫描二维码)
QQ:13721218 请注明“读者”
QQ群:128659835 请注明“读者”
在使用超级账本的过程中我发现一个问题,超级账本无法并发操作一个 key,stub.PutState 是异步执行,我们无法确认它是否执行完成,在没有执行完成之前再发起操作,就会产生覆盖。这个问题限制了超级账本的很多场景应用,这是超级账本的硬伤。
下面举一个例子来说明超级账本的问题 func (s *SmartContract) counter(stub shim.ChaincodeStubInterface, args []string) pb.Response { key := "counter" count,err = stub.GetState(key) count = count + 1 stub.PutState(key,count) return shim.Success(count) }
使用多线程请求chaincode中的counter函数100次。你会发现最终 count 并不等于 100。学习过多线程的朋友一定很清楚出了什么问题。
问题出在 stub.PutState 函数count还没有被写入,其他线程就开始读取stub.GetState(key),导致读取旧数据,最终计数器数字混乱。
很多场景需要更新区块中的数据,如果频繁操作,就会产生覆盖,目前Hyperledger Fabirc 并没有提供解决方案。
1. 我们不知道 stub.PutState是否执行完成,因为存储过程需要共识排序。
2. 超级账本没有提供事物处理或者互斥锁。
我的应用场景是实现代币功能,需要从总账号给注册用户转账,操作频繁。 从总账中减去 100 key := "coinbase" money,err = stub.GetState(key) money = money - 100 stub.PutState(key,money) 用户账号额度加100 key := "account" money,err = stub.GetState(key) money = money + 100 stub.PutState(key,money)
golang 提供的 mutex 也无法解决上面的问题,因为 mutex 锁只能工作在一个进程中。Peer / Orderer 节点不止一个。
使用 redis实现分布式锁或许能实现,但思考过后决定放弃,转为传统数据库。
另一个方案就是代币功能使用以太坊,其他需求使用超级账本。
区块链
2018-05-04 13:47:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 去中心化电商DApp实战开发
使用geth的account命令管理账户,例如创建新账户、更新账户密码、查询账户等: geth account [options...] [arguments...]
命令 - command list 列表显示现有账户 new 创建一个新的账户 update 修改账户 import 导入私钥创建新账户
可以使用 --help 获取这些命令的帮助信息,例如: ~$ geth account list --help list [command options] [arguments...] Print a short summary of all accounts OPTIONS: --datadir "/home/bas/.ethereum" Data directory for the databases and keystore --keystore Directory for the keystore (default = inside the datadir)
创建新账户
使用 geth account new 命令新建账户: $ geth account new Your new account is locked with a password. Please give a password. Do not forget this password. Passphrase: Repeat Passphrase: Address: {168bc315a2ee09042d83d7c5811b533620531f67}
导入私钥创建新账户
可以使用 geth account import 命令,通过导入私钥来创建一个新账户, ~$ geth account import --datadir /someOtherEthDataDir ./key.prv The new account will be encrypted with a passphrase. Please enter a passphrase now. Passphrase: Repeat Passphrase: Address: {7f444580bfef4b9bc7e14eb7fb2a029336b07c9d}
更新账户密码
使用 geth account update 命令来更新指定账户的密码: ~$ geth account update a94f5374fce5edbc8e2a8697c15331677e6ebf0b Unlocking account a94f5374fce5edbc8e2a8697c15331677e6ebf0b | Attempt 1/3 Passphrase: 0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b Account 'a94f5374fce5edbc8e2a8697c15331677e6ebf0b' unlocked. Please give a new password. Do not forget this password. Passphrase: Repeat Passphrase: 0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b
显示已有账户
使用 geth account list 命令列表显示已有账户,可选的可以使用 --keystore 指定keystore目录: ~$ geth account list --keystore /tmp/mykeystore/ Account #0: {5afdd78bdacb56ab1dad28741ea2a0e47fe41331} keystore:///tmp/mykeystore/UTC--2017-04-28T08-46-27.437847599Z--5afdd78bdacb56ab1dad28741ea2a0e47fe41331 Account #1: {9acb9ff906641a434803efb474c96a837756287f} keystore:///tmp/mykeystore/UTC--2017-04-28T08-46-52.180688336Z--9acb9ff906641a434803efb474c96a837756287f
原文: geth账户管理指南
区块链
2018-05-04 12:41:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
默认情况下,你在Geth中的账户是被锁住的,你不能用这些被锁住的账户发送交易,例如转账或调用合约方法。因此如果你需要使用Geth发送交易,就需要先解锁账户。那么,如何解锁Geth中的账户?
命令行解锁账户
一种方法是使用geth命令行来解锁指定的账户,例如: ~$ geth --unlock 0x3b3F14690C8Fb8b1B333Ff38961bdEEa658a3873 --password 7878
使用 --unlock 参数指定要解锁的账户地址; --password 参数是可选的,如果在命令行不指定这个参数,geth会提示你输入密码。
详细信息可参考 geth命令行参数使用说明 。
控制台解锁账户
另一种方法是在geth控制台使用js语句解锁指定的账户,例如: > personal.unlockAccount(‘0x3b3F14690C8Fb8b1B333Ff38961bdEEa658a3873 ’, ‘7878’)
同样,密码参数是可选的,如果你没有指定这个参数,geth控制台也会提示你输入密码。 如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 去中心化电商DApp实战开发
区块链
2018-05-04 11:12:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
本文将介绍如何在以太坊智能合约中实现代币的空投。区块链以太坊世界中所谓空投(airdrop),就是免费给你的区块链地址(公钥)发送代币。
代币空投的方式层出不穷,有手工打币空投的,也有向代币合约转账进行空投的,还可以无需转账,只需要将代币合约地址添加到imtoken钱包中去,就可以实现代币空投。本文将介绍这种无须动手的以太坊代币空投实现代码。
ERC-20代币
采用以太坊创建的ERC-20代币,指的是遵循ERC-20标准的代币,该标准指出,在代币合约中需要实现以下方法: balances: 余额变量,该变量里面存储了所有拥有代币的地址的余额 mapping(address => uint) balances; balanceOf():返回指定地址的账户余额 // balanceOf方法原型 function balanceOf(address _owner) constant returns (uint256 balance) transfer():转移 _value 数量的token到地址 _to // transfer方法原型 function transfer(address _to, uint256 _value) returns (bool success) transferFrom()
从地址_from发送数量为_value的token到地址_to // transferFrom方法原型 function transferFrom(address _from, address _to, uint256 _value) returns (bool success)
这里仅列出ERC-20的代币标准中要实现的部分方法,具体可以查看ERC20规范。
如何实现自动空投?
当在钱包中添加一个代币的合约时,钱包首先需要获取当前地址在该代币合约中的余额,这时钱包会调用了代币合约的 balanceOf() 方法,也就是虽然你在添加代币合约的时候。因此想要实现空投,只需要在balanceOf()方法里面实现一个空投的方法。
首先看一下,一个基本的balanceOf() 方法实现代码: function balanceOf(address _owner) public view returns (uint256 balance) { return balances[_owner]; }
基础的方法仅从 balances 变量中获取你当前地址的余额。
如果想要实现空投,可以这样: uint totalSupply = 100000000 ether; // 总发行量 uint currentTotalSupply = 0; // 已经空投数量 uint airdropNum = 1 ether; // 单个账户空投数量 function balanceOf(address _owner) public view returns (uint256 balance) { // 添加这个方法,当余额为0的时候直接空投 if (balances[_owner] == 0 && currentTotalSupply < totalSupply) { currentTotalSupply += airdropNum; balances[_owner] += airdropNum; } return balances[_owner]; }
可能你会说这样,我只需要将我地址里面的余额全部转出去,那么我又可以调用合约的balanceOf()方法进行空投,如果我想实现给每个地址仅空投一次,应该如何操作呢?
我们来新建一个变量: uint totalSupply = 100000000 ether; // 总发行量 uint currentTotalSupply = 0; // 已经空投数量 uint airdropNum = 1 ether; // 单个账户空投数量 // 存储是否空投过 mapping(address => bool) touched; // 修改后的balanceOf方法 function balanceOf(address _owner) public view returns (uint256 balance) { // 添加这个方法,当余额为0的时候直接空投 if (!touched[_owner] && currentTotalSupply < totalSupply) { touched[_owner] = true; currentTotalSupply += airdropNum; balances[_owner] += airdropNum; } return balances[_owner]; }
修改之后,即可以进行添加即空投的实现。
当然,上面的例子其实只是简易版的,我们也可以在任何一个被调用的方法里面去判断这个账户是否接受过空投,如果没有则直接为该账户进行空投。
本文根据网络资料整理。 如果你希望 高效的 学习以太坊DApp开发,可以访问汇智网提供的 最热门 在线互动教程: 适合区块链新手的以太坊DApp实战入门教程 区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问 这个以太坊博客 。
区块链
2018-05-04 09:11:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1、开发完成后页面预览如下

用户可以查看到当前参与投票的候选人名单已经他们各自的目前所得票数,选择其中一名进行投票,被投票的人所得票数对应增加。

2、开发准备工作 本地NodeJs环境 Ganache测试环境 熟悉基础的Web3-Api操作,安装命令为: npm install web3 NodeJs安装Solc编译环境,安装命令为:npm install solc
3、编写智能合约
创建工作目录 Voting-Node,在此目录下新建 Voting.sol 智能合约文件,并在Remix中对此文件进行编辑,最后编辑完成后的文件内容如下: pragma solidity ^0.4.18; contract Voting { mapping (bytes32 => uint8) public votesReceived; bytes32[] public candidateList; constructor (bytes32[] candidateNames) public { candidateList = candidateNames; } function totalVotesFor(bytes32 candidate) view public returns (uint8) { require(validCandidate(candidate)); return votesReceived[candidate]; } function voteForCandidate(bytes32 candidate) public { require(validCandidate(candidate)); votesReceived[candidate] += 1; } function validCandidate(bytes32 candidate) view public returns (bool) { for(uint i = 0; i < candidateList.length; i++) { if (candidateList[i] == candidate) { return true; } } return false; } }
4、编译、部署、测试 智能合约
在Voting-Node目录打开命令行并输入node 命令,可以进入REPL环境,分步执行编译部署命令,不过这种方式比较麻烦,不方便维护和修改,此处直接在Voting-Node目录中编写好了一个编译、部署、测试 的 depoy.js文件,直接在Nodejs中运行就可以看到结果,内容如下: //引入web3模块 let Web3 = require('web3'); //初始化 web3 let web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:7545")); //输出初始化结果 console.log('Initialization web3 complete,the first account is '+ web3.eth.accounts[0]); let fs = require('fs'); let code = fs.readFileSync('Voting.sol').toString(); let solc = require('solc'); //编译合约为ABI文件 let compiledCode = solc.compile(code); console.log('Compile Voting.sol complete'); //部署合约至区块链节点 let abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface); //写入ABI文件至本地文件目录 fs.writeFile('Voting.json',JSON.stringify(abiDefinition), {}, function(err) { console.log('write ABI file [Voting.json] complete . '); }); let VotingContract = web3.eth.contract(abiDefinition); let byteCode = compiledCode.contracts[':Voting'].bytecode; //调用VotingContract对象的new()方法来将投票合约部署到区块链。new()方法参数列表应当与合约的 构造函数要求相一致。对于投票合约而言,new()方法的第一个参数是候选人名单。 let deployedContract = VotingContract.new(['Rama','Nick','Jose'],{data: byteCode, from: web3.eth.accounts[0], gas: 4700000}); //输出合约 地址,如果此处没有返回地址,可以在Ganache日志中查看到 console.log('deploy complete,deploy address is '+ deployedContract.address); //let contractInstance = VotingContract.at(deployedContract.address); let contractInstance = VotingContract.at('0x99bdfb1f4c5d0c227d6cd98cf7a254bfc27c35cc'); //测试合约调用 contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]}); contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]}); contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]}); contractInstance.voteForCandidate('Nick', {from: web3.eth.accounts[0]}); contractInstance.voteForCandidate('Jose', {from: web3.eth.accounts[0]}); contractInstance.voteForCandidate('Jose', {from: web3.eth.accounts[0]}); console.log("--------------finish----------------"); let RamaVote=contractInstance.totalVotesFor.call('Rama'); let NickVote=contractInstance.totalVotesFor.call('Nick'); let JoseVote=contractInstance.totalVotesFor.call('Jose'); console.log("Rama's vote is "+RamaVote); console.log("Nick's vote is "+NickVote); console.log("Jose's vote is "+JoseVote);
执行结果如下: PS C:\Workspace\Ruoli-Code\Eth-Node> node .\deploy.js Initialization web3 complete,the first account is 0xa0b4360847bfe6c45f78103e97678dfa79aa9975 Compile Voting.sol complete deploy complete,deploy address is undefined --------------finish---------------- Rama's vote is 3 Nick's vote is 1 Jose's vote is 2 write ABI file complete .
最后结果符合预期,说明测试调用已经成功。 如果部署中出现 问题:TypeError: web3.eth.contract is not a function
则需要安装 0.19版本的 web3,命令如下:
npm install web3@^0.19.0 --save
安装完成后,问题修复。
5、网页交互
在完成上面的编写、编译、部署、测试环节后,添加网页交互那就很简单了。
在Voting-Node目录 打开命令行,执行初始化命令,如下: npm init -y
会在此目录下生成 package.json 文件,修改此文件内容如下: { "name": "Voting-Node", "version": "1.0.0", "description": "", "main": "index.js", "dependencies": { "solc": "^0.4.23", "web3": "^0.19.0" }, "devDependencies": {}, "scripts": { "dev": "node index.js" }, "keywords": [], "author": "Ruoli", "license": "ISC" }
在目录下新建 index.js 、 index.html、app.js 文件,如下所示。
index.js 内容如下: var http = require('http'); var fs = require('fs'); var url = require('url'); // 创建服务器 http.createServer( function (request, response) { // 解析请求,包括文件名 var pathname = url.parse(request.url).pathname; // 输出请求的文件名 console.log("Request for " + pathname + " received."); // 从文件系统中读取请求的文件内容 fs.readFile(pathname.substr(1), function (err, data) { if (err) { console.log(err); // HTTP 状态码: 404 : NOT FOUND // Content Type: text/plain response.writeHead(404, {'Content-Type': 'text/html'}); }else{ // HTTP 状态码: 200 : OK // Content Type: text/plain response.writeHead(200, {'Content-Type': 'text/html'}); // 响应文件内容 response.write(data.toString()); } // 发送响应数据 response.end(); }); }).listen(3000); // 控制台会输出以下信息 console.log('Server running at http://127.0.0.1:3000/index.html');   
index.html 内容如下: Voting DApp

简易投票 DApp


app.js 内容如下:
注意:此处需要将之前保存的 ABI文件内容引入。 let web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545")); //导入合约的ABI文件 let abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]') let VotingContract = web3.eth.contract(abi); let contractInstance = VotingContract.at('0x99bdfb1f4c5d0c227d6cd98cf7a254bfc27c35cc'); let candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"} $(document).ready(function() { //初始化余额 candidateNames = Object.keys(candidates); for (var i = 0; i < candidateNames.length; i++) { let name = candidateNames[i]; let val = contractInstance.totalVotesFor.call(name).toString() $("#"+candidates[name]).html(val); } //初始化事件 $(".vote-btn").click(function(){ //获取投票人名称 let voteName=$(this).prev().prev().text(); contractInstance.voteForCandidate(voteName, {from: web3.eth.accounts[0]}, function() { let div_id = candidates[voteName]; let vote_num=contractInstance.totalVotesFor.call(voteName).toString(); $("#"+div_id).fadeOut(400); $("#"+div_id).html(vote_num).fadeIn(800); }); }); });
至此所有开发已经完成。
6、网页访问与测试
执行如下命令启动服务: PS C:\Workspace\Ruoli-Code\Voting-Node> npm run dev > Voting-Node@1.0.0 dev C:\Workspace\Ruoli-Code\Voting-Node > node index.js Server running at http://127.0.0.1:3000/index.html
执行完成后 在浏览器中 访问:http://127.0.0.1:3000/index.html
即可看到最开始的界面,至此所有开发完成。
区块链
2018-05-03 22:13:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
ERC-20标准的内容
ERC-20标准中定义了以下函数接口: totalSupply():返回代币供给总量 balanceOf(address _owner):返回_owner的帐户余额 transfer(address _to,uint256 _value):并将数量为_value的代币转入地址_to并触发transfer事件 transferFrom(address _from,address _to,uint256_value):将地址_from中的_value数量的代币转入地址_to ,并触发transfer事件 approve(address _spender,uint256 _value):允许_spender提取限额_value的代币 allowance(address _owner,address _spender):返回_spender可从_owner提款的代币数量上限
以上函数将触发以下事件: transfer(address indexed _from,address indexed _to,uint256 _value):每次进行代币转账时都会触发 approval(address indexed _owner,address indexed _spender,uint256 _value):调用approve()方法将触发该事件
ERC-20于2015年提出并于2017年9月正式实施。这是代币标准化的一个很好的起点。 然而,开发者社区 已经注意到它存在一些缺陷和漏洞,此外,还有一些场景它不能很好的满足。因此陆续提出了其他的ERC标准。 如果你希望 高效的 学习以太坊DApp开发,可以访问汇智网提供的 最热门 在线互动教程: 适合区块链新手的以太坊DApp实战入门教程 区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问 这个以太坊博客 。
区块链
2018-05-03 20:34:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
ERC-223标准
以太坊开发人员Dexaran在一篇文章中详细描述了 ETC20 不适合的两种场景:
“在ERC20中执行交易有两种方式: transfer函数。 approve + transferFrom机制。
通证余额只是通证合约中的一个变量。
通证的交易是合约内部变量的变化。 转出账户的余额将减少,转入账户的余额将增加。
交易发生时, transfer()函数不会通知转入账户。 因此转入账户将无法识别传入的交易! 我写了一个例子,可以展示这一导致未处理的交易和资金损失的过程 。
因此,如果接收账户是合约,那么必须使用approve + transferFrom机制来发送通证。 如果接受账户是外部拥有帐户,则必须通过transfer函数发送通证。 如果选择了错误的机制, 通证将卡在合约内(合约将不会识别交易),没有办法来提取这些卡壳的通证。“
他对这个问题提出的解决方案包含在ERC-223中 。 它与ERC-20标准非常相似,但解决了上述问题。 当通证转移到智能合约账户时,该合约的特殊函数tokenFallback() 允许接收方合约拒绝令牌或触发 进一步的操作。 在大多数情况下,这可以用来代替approve()函数。 标准状态:标准打开 建议日期:3/5/2017 如果你希望 高效的 学习以太坊DApp开发,可以访问汇智网提供的 最热门 在线互动教程: 适合区块链新手的以太坊DApp实战入门教程 区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问 这个以太坊博客 。
区块链
2018-05-03 20:30:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
MetaMask是一个开源的以太坊钱包,能帮助用户方便地管理自己的以太坊数字资产, 但在国内由于网络原因,你可能下载不了。本文将介绍如何解决metamask钱包无法下载的问题。
你可以按照以下操作步骤完成MetaMask钱包的安装:
STEP 1 点击链接: https://github.com/MetaMask/metamask-extension/releases
STEP 2 点击 “Assets” 列表下的 "metamask-chrome-4.4.0.zip" (或你看到的最新版), 下载并解压此压缩包
STEP 3 用谷歌浏览器 (Chrome) 打开链接: chrome://extensions:
选择 "加载已解压的扩展程序” (Load unpacked extension),在跳出菜单中选择刚才解压的文件包
STEP 4 网页将跳转到新的页面,选择 “Get Chrome Extension"
执行上述步骤后,浏览器右上角将出现一个新图标(MetaMask 狐狸插件图标), 成功安装 MetaMask 钱包。
如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 去中心化电商DApp实战开发
区块链
2018-05-03 20:24:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
ERC827
以太坊通证标准 ERC-20 的另一个扩展是ERC-827标准。它允许转让通证并允许持有人允许第三方使用通证。 以太坊上的通证可以被其他应用程序重复使用,这其中也包括钱包和交易所。 当需要支持第三方动态消费限额调整时这一点非常有用。
最重要的是,由于ERC-827是ERC-20的延伸,它也与ERC-20兼容。
一些提议的接口函数包括: transferFrom(address _from,address _to,uint256 _value,bytes _data) returns (bool success) approve(address _spender,uint256 _value,bytes _data) returns (bool success) 标准状态:打开 建议日期:1/12/2018 如果你希望 高效的 学习以太坊DApp开发,可以访问汇智网提供的 最热门 在线互动教程: 适合区块链新手的以太坊DApp实战入门教程 区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问 这个以太坊博客 。
区块链
2018-05-03 19:53:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
以太坊仿真器ganache-cli启动后,如何获取其网络编号( network id )?是否可以自定义ganache-cli的网络编号?本文将解答这些问题。 如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 去中心化电商DApp实战开发
获取ganache-cli的network id
参考 ganache-cli命令行参数说明 一文,可以了解默认情况下,ganache-cli使用启动时的unix时间作为network id。
可以启动ganache-cli,然后使用geth连接到运行中的ganache-cli,然后在geth控制台验证这一点: ~$ geth attach http://localhost:8545 > web3.version.network "1514781296000"
1514781296000 就是ganache-cli启动时的unix时间,简单的js脚本将其转化为Date对象,对应的时间是2018-1-1 12:34:56: new Date(1514781296000) // Mon Jan 01 2018 12:34:56 GMT+0800 (中国标准时间)
设置ganache-cli的network id
如果需要固定的网络编号,可以使用 -i 或 --networkId 启动选项来进行设置。例如,下面的命令将network id设置为1234: ~$ ganache-cli -i 1234
区块链
2018-05-03 19:47:06
「深度学习福利」大神带你进阶工程师,立即查看>>>
go-ethereum客户端通常被称为geth,它是个命令行界面,执行在Go上实现的完整以太坊节点。通过安装和运行geth,可以参与到以太坊前台实时网络并进行以下操作: 挖掘真的以太币 在不同地址间转移资金 创建合约,发送交易 探索区块历史 及很多其他
链接:
网站: http://ethereum.github.io/go-ethereum/
Github: https://github.com/ethereum/go-ethereum
维基百科: https://github.com/ethereum/go-ethereum/wiki/geth
Gitter: https://gitter.im/ethereum/go-ethereum 如果你希望 高效的 学习以太坊DApp开发,可以访问汇智网提供的 最热门 在线互动教程: 适合区块链新手的以太坊DApp实战入门教程 区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问 这个以太坊博客 。
区块链
2018-05-02 10:50:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
ERC-621
ERC-621是以太坊标准 ERC-20 的扩展。 它增加了两个额外的功能, increaseSupply和decreaseSupply 。 这可以增加和减少流通中的令牌供应。 ERC-20只允许单一的通证发放事件。 这将供应量限制在一个固定的不可改变的数目。 ERC-621建议totalSupply应当是可修改的。 状态:打开 建议日期:2017/5/1 如果你希望 高效的 学习以太坊DApp开发,可以访问汇智网提供的 最热门 在线互动教程: 适合区块链新手的以太坊DApp实战入门教程 区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问 这个以太坊博客 。
区块链
2018-05-03 19:42:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
ERC-721
ERC-721标准与其他以太坊通证标准 ERC-20 和 ERC-223 都大不相同。 它描述了一个不可互换的通证。这意味着每个通证是完全不同的,并且每个通证对不同的用户都有不同的价值。 理解这种通证的一个方法就是回忆CryptoKittes。 每一个数字猫都是独立的,其价值取决于其稀缺性和用户的购买欲。
ERC-721令牌可用于任何交易所,但通证价值是“每个通证的唯一性和稀缺性所决定的结果”。标准中规定的接口函数包括name、symbol、totalSupply、balanceOf、ownerOf、approve、takeOwnership 、 transfer 、tokenOfOwnerByIndex和tokenMetadata 。 它还定义了两个事件: Transfer和Approval 。 Gerald Nash的 这篇文章很好地解释了可互换性的概念。 标准状态:打开 建议日期:9/22/2017 如果你希望 高效的 学习以太坊DApp开发,可以访问汇智网提供的 最热门 在线互动教程: 适合区块链新手的以太坊DApp实战入门教程 区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问 这个以太坊博客 。
区块链
2018-05-03 19:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如果你熟悉标准的以太坊节点软件geth的使用,就会希望了解,如何让ganache-cli启动后也进入控制台模式?本文将介绍这一问题的解决方法。 如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 去中心化电商DApp实战开发
参考 《ganache-cli命令行参数说明》 这篇文章,容易注意到ganache-cli并没有提供进入控制台模式的启动选项,你能做的就是设置一下初始账户、监听端口、网络id、gas价格之类的参数。
解决的方法是使用 geth 的 attach 命令,让geth连接到当前运行中的ganache-cli,从而获得一个控制台。操作步骤如下:
1、启动ganache-cli
在一个终端里启动ganache-cli,例如: ~$ ganache-cli
默认情况下,ganache-cli将在8545端口监听,我们可以让geth加入这个正在运行的ganache-cli。
2、将geth连接到ganache-cli
在另一个终端里执行geth的attach子命令,连接到运行中的ganache-cli: ~$ geth attach http://localhost:8545 >
熟悉的控制台又回来了。
原文链接: ganache-cli如何进入控制台模式
区块链
2018-05-03 19:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
仿真器ganache-cli是开发以太坊智能合约和去中心化应用(DApp)必不可少的工具软件,本文将介绍启动ganache-cli时如何自定义设置其监听端口和监听地址。
指定监听端口
默认情况下,ganache-cli在8545端口监听,可以使用 -p 或 --port 启动选项改变这一默认行为。例如,下面的命令将启动ganache-cli在7878端口监听: ~$ ganache-cli -p 7878
指定监听地址
默认情况下,ganache-cli的监听地址为 0.0.0.0 ,这意味着从仿真器所在网络上的任意计算机都可以访问这个节点。可以使用 -h 或 --host 启动选项改变这一默认行为。例如,下面的命令将启动ganache-cli在地址 127.0.0.1 监听,这样只有本机上的应用才可以访问仿真节点: ~$ ganache-cli -h 127.0.0.1
同时指定监听地址和监听端口
同时使用 -h 和 -p 启动选项来设定监听地址和端口。例如,下面的命令将启动ganache-cli在地址 127.0.0.1 的7878端口监听: ~$ ganache-cli -h 127.0.0.1 -p 7878 如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 以太坊去中心化电商DApp实战开发
参考资料: ganache-cli命令行参数说明
区块链
2018-05-03 18:46:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
最著名的两个以太坊通证标准是代币标准ERC20和数字资产标准ERC721。在本文中,除了介绍这两个流行的ERC以太坊标准,还将介绍其他一些针对特定应用场景的ERC20改进标准:ERC223、ERC621和ERC827。
什么是ERC?
ERC代表“Etuereum Request for Comment",这是Ethereum版的意见征求稿 (RFC),RFC是由互联网工程任务组制定的一个概念。 RFC中的备忘录包含技术和组织注意事项。 对于ERC,意见征求稿中包括一些关于以太坊网络建设的技术指导。
ERC是Ethereum开发者为以太坊社区编写的。 因此,ERC的创建流程中包括开发人员。 为了创建一个以太坊平台的标准,开发人员应当提交了一个以太坊改进方案(EIP), 改进方案中包括协议规范和合约标准。 一旦EIP被委员会批准并最终确定,它就成为ERC。 EIP的完整列表可以在 这里 找到。
最终确定的EIP为以太坊开发者提供了一套可实施的标准。 这使得智能合约可以遵循这些通用的接口标准来构建。
ERC-20是整个加密社区中最为人熟知的标准,在Ethereum平台之上发布的大多数通证( token )都使用它。
ERC-20标准的内容
ERC-20标准中定义了以下函数接口: totalSupply():返回代币供给总量 balanceOf(address _owner):返回_owner的帐户余额 transfer(address _to,uint256 _value):并将数量为_value的代币转入地址_to并触发transfer事件 transferFrom(address _from,address _to,uint256_value):将地址_from中的_value数量的代币转入地址_to ,并触发transfer事件 approve(address _spender,uint256 _value):允许_spender提取限额_value的代币 allowance(address _owner,address _spender):返回_spender可从_owner提款的代币数量上限
以上函数将触发以下事件: transfer(address indexed _from,address indexed _to,uint256 _value):每次进行代币转账时都会触发 approval(address indexed _owner,address indexed _spender,uint256 _value):调用approve()方法将触发该事件
ERC-20于2015年提出并于2017年9月正式实施。这是代币标准化的一个很好的起点。 然而,开发者社区 已经注意到它存在一些缺陷和漏洞,此外,还有一些场景它不能很好的满足。因此陆续提出了其他的ERC标准。
ERC-223 状态:打开 建议日期:3/5/2017
开发人员Dexaran在一篇文章中详细描述了ETC20不适合的两种场景:
“在ERC20中执行交易有两种方式: transfer函数。 approve + transferFrom机制。
通证余额只是通证合约中的一个变量。
通证的交易是合约内部变量的变化。 转出账户的余额将减少,转入账户的余额将增加。
交易发生时, transfer()函数不会通知转入账户。 因此转入账户将无法识别传入的交易! 我写了一个例子,可以展示这一导致未处理的交易和资金损失的过程 。
因此,如果接收账户是合约,那么必须使用approve + transferFrom机制来发送通证。
如果接受账户是外部拥有帐户,则必须通过transfer函数发送通证。 如果选择了错误的机制,通证将卡在合约内(合约将不会识别交易),没有办法来提取这些卡壳的通证。“
他对这个问题提出的解决方案包含在ERC-223中 。 它与ERC-20标准非常相似,但解决了上述问题。当通证转移到智能合约账户时,该合约的特殊函数tokenFallback() 允许接收方合约拒绝令牌或触发进一步的操作。 大多数情况下,这可以用来代替approve()函数。
ERC-621 状态:打开 建议日期:2017/5/1
ERC-621是ERC-20标准的扩展。 它增加了两个额外的功能, increaseSupply和decreaseSupply 。这可以增加和减少流通中的令牌供应。 ERC-20只允许单一的通证发放事件。 这将供应量限制在一个固定的不可改变的数目。 ERC-621建议totalSupply应当是可修改的。
ERC-721 状态:打开 建议日期:9/22/2017
ERC-721与ERC-20和ERC-223都大不相同。 它描述了一个不可互换的通证。 这意味着每个通证是完全不同的,并且每个通证对不同的用户都有不同的价值。 理解这种通证的一个方法就是回忆CryptoKittes。 每一个数字猫都是独立的,其价值取决于其稀缺性和用户的购买欲。
ERC-721令牌可用于任何交易所,但通证价值是“每个通证的唯一性和稀缺性所决定的结果”。标准中规定的接口函数包括name、symbol、totalSupply、balanceOf、ownerOf、approve、takeOwnership 、 transfer 、tokenOfOwnerByIndex和tokenMetadata 。 它还定义了两个事件: Transfer和Approval 。 Gerald Nash的 这篇文章很好地解释了可互换性的概念。
ERC-827 状态:打开 建议日期:1/12/2018
ERC-20标准的另一个扩展是ERC-827。 它允许转让通证并允许持有人允许第三方使用通证。 以太坊上的通证可以被其他应用程序重复使用,这其中也包括钱包和交易所。 当需要支持第三方动态消费限额调整时这一点非常有用。
最重要的是,由于ERC-827是ERC-20的延伸,它也与ERC-20兼容。
一些提议的接口函数包括: transferFrom(address _from,address _to,uint256 _value,bytes _data) returns (bool success) approve(address _spender,uint256 _value,bytes _data) returns (bool success) 如果你希望 高效的 学习以太坊DApp开发,可以访问汇智网提供的 最热门 在线互动教程: 适合区块链新手的以太坊DApp实战入门教程 区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问 这个以太坊博客 。
区块链
2018-05-03 18:22:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
设计模式是许多开发场景中的首选解决方案,本文将介绍五种经典的以太坊智能合约设计模式并给出以太坊solidity实现代码:自毁合约、工厂合约、名称注册表、映射表迭代器和提款模式。
1、自毁合约
合约自毁模式用于终止一个合约,这意味着将从区块链上永久删除这个合约。 一旦被销毁,就不可能调用合约的功能,也不会在账本中记录交易。
现在的问题是:“为什么我要销毁合约?”。
有很多原因,比如某些定时合约,或者那些一旦达到里程碑就必须终止的合约。 一个典型的案例是贷款合约,它应当在贷款还清后自动销毁;另一个案例是基于时间的拍卖合约,它应当在拍卖结束后终止 —— 假设我们不需要在链上保存拍卖的历史记录。
在处理一个被销毁的合约时,有一些需要注意的问题: 合约销毁后,发送给该合约的交易将失败 任何发送给被销毁合约的资金,都将永远丢失
为避免资金损失,应当在发送资金前确保目标合约仍然存在,移除所有对已销毁合约的引用。
现在我们来看看代码: contract SelfDesctructionContract { public address owner; public string someValue; modifier ownerRestricted { require(owner == msg.sender); _; } // constructor function SelfDesctructionContract() { owner = msg.sender; } // a simple setter function function setSomeValue(string value){ someValue = value; } // you can call it anything you want function destroyContract() ownerRestricted { suicide(owner); } }
正如你所看到的, destroyContract() 方法负责销毁合约。
请注意,我们使用自定义的 ownerRestricted 修饰符来显示该方法的调用者,即仅允许合约的拥有者 销毁合约。
2、工厂合约
工厂合约用于创建和部署“子”合约。 这些子合约可以被称为“资产”,可以表示现实生活中的房子或汽车。
工厂用于存储子合约的地址,以便在必要时提取使用。 你可能会问,为什么不把它们存在Web应用数据库里?
这是因为将这些地址数据存在工厂合约里,就意味着是存在区块链上,因此更加安全,而数据库的损坏可能会造成资产地址的丢失,从而导致丢失对这些资产合约的引用。 除此之外,你还需要跟踪所有新创建的子合约以便同步更新数据库。
工厂合约的一个常见用例是销售资产并跟踪这些资产(例如,谁是资产的所有者)。 需要向负责部署资产的函数添加payable修饰符以便销售资产。 代码如下: contract CarShop { address[] carAssets; function createChildContract(string brand, string model) public payable { // insert check if the sent ether is enough to cover the car asset ... address newCarAsset = new CarAsset(brand, model, msg.sender); carAssets.push(newCarAsset); } function getDeployedChildContracts() public view returns (address[]) { return carAssets; } } contract CarAsset { string public brand; string public model; address public owner; function CarAsset(string _brand, string _model, address _owner) public { brand = _brand; model = _model; owner = _owner; } }
代码 address newCarAsset = new CarAsset(...) 将触发一个交易来部署子合约并返回该合约的地址。 由于工厂合约和资产合约之间唯一的联系是变量 address[] carAssets ,所以一定要正确保存子合约的地址。
3、名称注册表
假设你正在构建一个依赖与多个合约的DApp,例如一个基于区块链的在线商城,这个DApp使用了ClothesFactoryContract、GamesFactoryContract、BooksFactoryContract等多个合约。
现在想象一下,将所有这些合约的地址写在你的应用代码中。 如果这些合约的地址随着时间的推移而变化,那该怎么办?
这就是名称注册表的作用,这个模式允许你只在代码中固定一个合约的地址,而不是数十、数百甚至数千个地址。它的原理是使用一个合约名称 => 合约地址的映射表,因此可以通过调用 getAddress("ClothesFactory") 从DApp内查找每个合约的地址。 使用名称注册表的好处是,即使更新那些合约,DApp也不会受到任何影响,因为我们只需要修改映射表中合约的地址。
代码如下: contract NameRegistry { struct ContractDetails { address owner; address contractAddress; uint16 version; } mapping(string => ContractDetails) registry; function registerName(string name, address addr, uint16 ver) returns (bool) { // versions should start from 1 require(ver >= 1); ContractDetails memory info = registry[name]; require(info.owner == msg.sender); // create info if it doesn't exist in the registry if (info.contractAddress == address(0)) { info = ContractDetails({ owner: msg.sender, contractAddress: addr, version: ver }); } else { info.version = ver; info.contractAddress = addr; } // update record in the registry registry[name] = info; return true; } function getContractDetails(string name) constant returns(address, uint16) { return (registry[name].contractAddress, registry[name].version); } }
你的DApp将使用 getContractDetails(name) 来获取指定合约的地址和版本。
4、映射表迭代器
很多时候我们需要对一个映射表进行迭代操作 ,但由于Solidity中的映射表只能存储值,并不支持迭代,因此映射表迭代器模式非常有用。 需要指出的是,随着成员数量的增加,迭代操作的复杂性会增加,存储成本也会增加,因此请尽可能地避免迭代。
实现代码如下: contract MappingIterator { mapping(string => address) elements; string[] keys; function put(string key, address addr) returns (bool) { bool exists = elements[key] == address(0) if (!exists) { keys.push(key); } elements[key] = addr; return true; } function getKeyCount() constant returns (uint) { return keys.length; } function getElementAtIndex(uint index) returns (address) { return elements[keys[index]]; } function getElement(string name) returns (address) { return elements[name]; } }
实现 put() 函数的一个常见错误,是通过遍历来检查指定的键是否存在。正确的做法是 elements[key] == address(0) 。虽然遍历检查的做法不完全是一个错误,但它并不可取,因为随着keys数组的增长,迭代成本越来越高,因此应该尽可能避免迭代。
5、提款模式
假设你销售汽车轮胎,不幸的是卖出的所有轮胎出问题了,于是你决定向所有的买家退款。
假设你跟踪记录了合约中的所有买家,并且合约有一个refund()函数,该函数会遍历所有买家并将钱一一返还。
你可以选择 - 使用buyerAddress.transfer()或buyerAddress.send() 。 这两个函数的区别在于,在交易异常时,send()不会抛出异常,而只是返回布尔值false ,而transfer()则会抛出异常。
为什么这一点很重要?
假设大多数买家是外部账户(即个人),但一些买家是其他合约(也许是商业)。 假设在这些买方合约中,有一个合约,其开发者在其fallback函数中犯了一个错误,并且在被调用时抛出一个异常,fallback()函数是合约中的默认函数,如果将交易发送到合同但没有指定任何方法,将调用合约的fallback()函数。 现在,只要我们在refund函数中调用contractWithError.transfer() ,就会抛出异常并停止迭代遍历。 因此,任何一个买家合约的fallback()异常都将导致整个退款交易被回滚,导致没有一个买家可以得到退款。
虽然在一次调用中退款所有买家可以使用send()来实现,但是更好的方式是提供withdrawFunds()方法,它将单独按需要退款给调用者。 因此,错误的合约不会应用其他买家拿到退款。
实现代码如下: contract WithdrawalContract { mapping(address => uint) buyers; function buy() payable { require(msg.value > 0); buyers[msg.sender] = msg.value; } function withdraw() { uint amount = buyers[msg.sender]; require(amount > 0); buyers[msg.sender] = 0; require(msg.sender.send(amount)); } } 如果你希望 高效的 学习以太坊DApp开发,可以访问汇智网提供的 最热门 在线互动教程: 适合区块链新手的以太坊DApp实战入门教程 区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问 这个以太坊博客 。
区块链
2018-05-03 18:13:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
超过100道以太坊区块链开发技术岗位的面试题,附参考答案。面试题目涵盖 以太坊的基本概念、Geth客户端使用、智能合约基本概念、Solidity开发语言、去中心化 应用DApp、web3.js开发库等方面。 如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 去中心化电商DApp实战开发
以太坊
问:以太坊的有价通证叫什么?
答:以太(ETH:Ether)
问:Wei和以太有什么区别?
答:Wei是一个面额,像美分到美元或便士到磅。 1 ETH =10^18 Wei
问:以太坊的平均出块时间是多少?
答:大约14秒
问:以太坊的平均块大小是多少?
答:大约2KB,实际值取决于具体情况。
问:以太坊是否支持脚本? 如果是这样,支持什么类型的脚本?
答:是的。 它支持智能合约
问:你如何得到以太?
答:有几种方法: 1.成为一名矿工
2.用其他货币换取
3.使用以太Faucet,例如 https://faucet.metamask.io
4.接受别人的赠送
问:以太从哪里来的?
答:在2014年预售中首次创建了6000万个。另外,在挖出新块时也会生成以太。
问:什么是节点?
答:一个节点本质上是一台连接到网络的计算机,它负责处理交易。
问:你熟悉多少种以太坊网络?
答:有三种类型的网络 - 实时网络(主),测试网络(如Ropsten和Rinkeby)和私有网络。
问:与以太坊网络交互的方式有哪些?
答:可以使用电子钱包或DApp
问:你可以“隐藏”一个以太坊交易吗?
答:不可以。所有交易对每个人都是可见的。
问:交易记录在哪里?
答:在公共账本上。
问:这些网络的ID是什么?
答:Live(id = 1),Ropsten(id = 3),Rinkeby(id = 4),Private(由开发人员分配)
问:我可以在Rinkeby测试网络中挖一些以太,然后转移到Live网络吗?
答:不可以。不能在不同的以太坊网络之间传递以太。
问:为什么需要私有网络?
答:有很多原因,但主要是为了数据隐私、分布式数据库、权限控制和测试。
问:你如何轻松查看有关交易和区块的详细信息?
答:使用区块链浏览器,如etherscan.io或live.ether.camp
问:私有网络的交易和区块信息怎么查看呢?
答:可以使用开源浏览器客户端,例如 https://github.com/etherparty/explorer
问:区块链的共识是什么?
答:遵循特定协议(如以太坊)验证交易(创建块)的过程。
问:区块链中两种常用的共识模型是什么?
答:工作量证明(POW)和权益证明(POS)。
问:简单地解释下工作量证明。
答:它实际上是矿工为了证明自己的工作量并验证交易而对一个计算密集型问题的求解。
问:以简单的方式解释权益证明。
答:区块的创建者是根据节点所持有的财富和股权随机选择的。 它不是计算密集型的。
问:以太坊使用什么共识模式?
答:截至2018年初,它使用工作量证明,但今后将切换到权益证明。
问:怎么挖以太币?
答:使用钱包或geth客户端。
问:用什么来对交易进行签名?
答:用户的私钥。
问:丢失私钥后还能恢复以太坊账户吗?
答:可以,可以使用助记词组。
以太坊节点软件(Geth)
问:有哪些方法可以连接到一个以太坊节点?
答:IPC-RPC、JSON-RPC和WS-RPC。
问:那么Geth是什么?
答:Geth是以太坊的客户端。
问:连接到geth客户端的默认方式是什么?
答:默认情况下启用IPC-RPC,其他RPC都被禁用。
问:你知道geth的哪些API?
答:Admin、eth、web3、miner、net、personal、shh、debug和txpool。
问:你可以使用哪些RPC通过网络连接到geth客户端?
答:可以使用JSON-RPC和WS-RPC通过网络连接到geth客户端。 IPC-RPC只能连接到同一台机器上的geth客户端。
问:如果启动geth时使用了-rpc选项,哪些RPC会被启用?
答:JSON-RPC。
问:哪些RPC API是默认启用的?
答:eth、web3和net。
问:如何为JSON RPC启用Admin API?
答:使用-rpcapi选项。
问:选项-datadir有什么作用?
答:它指定了区块链的存储位置。
问:什么是geth的“快速”同步,为什么它更快?
答:快速同步会将事务处理回执与区块一起下载并完整提取最新的状态数据库,而不是重新执行所有发生过的交易。
问:选项--testnet是做什么的?
答:它将客户端连接到Ropsten网络。
问:启动geth客户端会在屏幕上输出大量文字,应该如何减少输出信息?
答:可以将--verbosity设置为较低的数字(默认值为3)
问:如何使用IPC-RPC将一个geth客户端连接到另一个客户端?
答:首先启动一个geth客户端,复制它的管道位置,然后使用同一个datadir启动另一个geth客户端并使用--attach 选项传入管道位置。
问:如何将自定义javascript文件加载到geth控制台中?
答:通使用--preload选项传入js文件的路径。
问:geth客户端的帐户存储在哪里?
答:在keystore目录中。
问:为了进行交易,需要对账户进行什么操作?
答:必须先解锁该账户 - 可以传入账户地址或账户序号来解锁。 也可以使用--password选项传入一个密码文件, 其中包含每个账户的密码。
问:你提到了一些有关账户序号的内容。 什么因素决定账户的序号?
答:添加帐户的先后顺序。
问:是否可以使用geth进行挖矿?
答:可以,使用--mine选项开启。
问:什么是“etherbase”?
答:这是接收挖矿奖励的帐户,它是序号为0的帐户。
智能合约和Solidity
问:什么是智能合约?
答:这是用多种语言编写的计算机代码。 智能合约存在于以太坊网络上,它们根据预定规则执行动作,规则是由 参与者在这些合约中商定的。
问:智能合约可以使用哪些语言编写?
答:Solidity,这是最常用的语言,也可以使用Serpent和LLL。
问:你能举出一个智能合约的用例吗?
答:卖方-买方应用场景:买方在智能合约中存入款项,卖方看到存款并发送货物,买方收到货物并 放行付款。
问:什么是Metamask?
答:Metamask是一个可以帮助用户在浏览器中与以太坊网络进行交互的工具
问:Metamask使用哪个以太坊节点?
答:它使用infura.io
问:Metamask不支持什么?
答:挖矿和合约部署。
问:执行合约是否免费?
答:不,调用合约方法是一个交易,因此需要支付费用。
问:访问智能合约的状态是否免费?
答:是的,查询状态不是交易。
问:谁执行合同?
答:矿工。
问:为什么调用智能合约的方法需要付费?
答:有些方法不会修改合约的状态,也没有其他逻辑,只是返回一个值,这样的方法是可以免费调用的。 调用那些改变合约状态的方法则需要付费,因为它们需要gas来执行。
问:为什么需要gas?
答:由于矿工在他们的机器上执行合约代码,他们需要gas来覆盖执行合约代码的成本。
问:是不是gas的价格决定了交易什么时候被处理?
答:即是,也不是。 gas价格越高,交易成功的可能性就越大。 尽管如此,gas价格并不能保证更快的交易处理。
问:交易中的gas使用量取决于什么?
答:这取决于合约所用的存储量、指令(操作码)的类型和数量。 每个EVM操作码都对应一个固定的gas用量。
问:交易费是如何计算的?
答:gas用量*gas价格(由调用方指定gas价格)
问:如果智能合约的执行成本低于调用方指定的gas用量,用户是否得到退款?
答:是的
问:如果智能合约的执行成本高于指定的gas用量,会发生什么情况?
答:用户不会得到退款,并且一旦所有的gas用完,执行就会停止,合约也不会改变。
问:谁支付智能合约的调用费用?
答:调用合约的用户。
问:节点在什么上面运行智能合约代码?
答:EVM - 以太坊虚拟机。 EVM遵循EVM规范,该规范是以太坊协议的组成部分。 EVM只是节点上的一个进程。
问:为了运行智能合同,EVM需要什么?
答:它需要合约的字节码,是通过编译Solidity等更高级别的语言编写的合约来生成字节码。
问:粗略的说,EVM有哪些组成部分?
答:内存区域、堆栈和执行引擎。
问:什么是Remix?
答:开发,测试和部署合约的在线工具。 适合快速构建和测试轻量级合约,但不适合更复杂的合约。
问:在Remix中,可以连接哪些节点?
答:可以使用Metamask连接到公共节点、也可以链接到使用Geth搭建的本地节点,或者在Javascript VM中模拟的内存节点。
问:什么是DApp,它与App有什么不同?有什么不同?
答:App通常包含一个客户端,这个客户端会与一些中心化的资源(由一个组织拥有)进行通信, 通常客户端通过一个中间层连接到中心化的数据层,如果中心化的数据层中的信息丢失,不能很轻松地恢复。 DApp表示去中心化应用程序。 DApps通过智能合约与区块链网络进行交互。 DApp使用的数据驻留在合约实例中。 中心化数据可能比去中心化数据更容易受到破坏。
DApps和web3
问:DApp的前端是否局限于某些技术或框架?
答:不受限制。可以使用任何技术来开发DApp的前端,比如HTML,CSS,JS,Java,Python...
问:前端用什么库连接后端(智能合同)?
答:Web3.js库。
问:在DApp的前端需要哪些东西才能与指定的智能合约进行交互?
答:合约的ABI和字节码。
问:ABI有什么作用?
答:ABI是合约的公开接口描述对象,被DApps用于调用合约的接口。
问:字节码有什么作用?
答:节点上的EVM只能执行合约的字节码。
问:为什么要使用BigNumber库?
答:因为Javascript不能正确处理大数。
问:为什么需要检查在Web DApp代码的开始部分是否设置了web3提供器(Provider)?
答:因为Metamask会注入一个web3对象,它覆盖其他的web3设置。
问:为什么要使用web3.js版本1.x而不是0.2x.x?
答:主要是因为1.x的异步调用使用Promise而不是回调,Promise目前在javascript世界中 是处理异步调用的首选方案。
问:如何在web3 1.x中列出账户?
答:web3.eth.getAccounts
问:.call和.send有什么区别?
答:.send发送交易并支付费用,而.call查询合约状态。
问:这样发送1个以太对吗: .send({value:1}) ?
A:不对,这样发送的是1 wei。 交易中总是以wei为单位。
问:那么为了发送1个以太,我必须将这个值乘以10^18?
答:可以使用 web3.utils.toWei(1,'ether') 。
问:调用 .send() 时需要指定什么?
答:必须指定 from 字段,即发送账户地址。 其他一切都是可选的。
问: web3.eth.sendTransaction() 的唯一功能是将以太发送到特定的地址,这个说法是否正确?
答:不对,也可以用它调用合约方法。
问:你是否知道以太坊的可扩展性解决方案?
答:2层协议。可能的解决方案是状态通道( state channels )和Plasma。
Solidity
问:Solidity是静态类型的还是动态类型的语言?
答:它是静态类型语言,这意味着类型在编译时是已知的。
问:Solidity中与Java“Class”类似的是什么?
答:合约。
问:什么是合约实例?
答:合约实例是区块链上已部署的合约。
问:请说出Java和Solidity之间的一些区别。
答:Solidity支持多重继承,但不支持重载。
问:你必须在Solidity文件中指定的第一件事是什么?
答:Solidity编译器的版本,比如指定为^ 0.4.8。 这是必要的,因为这样可以防止在使用其他版本 的编译器时引入不兼容性错误。
问:合约中包含什么?
答:主要由存储变量、函数和事件组成。
问:合约中有哪些类型的函数?
答:有构造函数、fallback函数、修改合约状态的函数和只读的constant函数。
问:如果我将多个合约定义放入单个Solidity文件中,我会得到什么错误?
答:将多个合约定义放入单个Solidity文件是完全正确的。
问:两个合约之间交互的方式有哪些?
答:一个合约可以调用另一个合约,也可以继承其他合约。
问:当你尝试使用部署一个包含多个合约的文件时会发生什么?
答:编译器只会部署该文件中的最后一个合约,而忽略所有其他合约。
问:如果我有一个大项目,我需要将所有相关的合约保存到一个文件中吗?
答:不需要。可以使用import语句导入其他合约文件,例如 import "./MyOtherContracts.sol"; 。
问:我只能导入本地合约文件吗?
答:还可以使用HTTP协议导入其他合约文件,例如从Github导入: import "http://github.com/owner/repo/path_to_file"; 。
问:EVM的内存分成了哪些部分?
答:它分为Storage、Memory和Calldata。
问:请解释一下Storage。
答:可以把它想象成一个数据库。 每个合约管理自己的Storage变量。 它是一个键-值数据库(256位键值)。 就每次执行使用的gas而言,在Storage上读取和写入的成本更高。
问:请解释一下Memory。
答:这是一个临时存储区。 一旦执行结束,数据就会丢失。 可以在Memory上分配像数组和结构这样复杂的数据类型。
问:请解释一下Calldata 。
答:可以把calldata视为一个调用堆栈。 它是临时的、不可修改的,用来存储EVM的执行数据。
问:哪些变量存储在Storage,那些变量存储在Memory?
答:状态变量和局部变量(它们是对状态变量的引用)存储在Storage区域, 函数参数位于Memory区域。
问:看看下面的代码,并解释代码的哪一部分对应于哪个内存区域: contract MyContract { // part 1 uint count; uint[] totalPoints; function localVars(){ // part 2 uint[] localArr; // part 3 uint[] memory memoryArr; // part 4 uint[] pointer = totalPoints; } }
答: 第1部分 - Storage
第2部分 - Storage
第3部分 - Memory
第4部分 - Storage
问:这样做对吗: function doSomething(uint[] storage args) internal returns(uint[] storage data) {…}
答:可以,可以强制将函数的参数设置为Storage存储。 在这种情况下,如果没有传递存储引用,编译器 会报错。
原文: 以太坊开发面试题及答案
区块链
2018-05-03 17:12:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
以太坊的一个 加密僵尸游戏 。 为了便于记忆,将其中的教程产出code贴了出来。
ZombieFactory.sol pragma solidity ^0.4.19; import "./ownable.sol"; contract ZombieFactory is Ownable { event NewZombie(uint zombieId, string name, uint dna); uint dnaDigits = 16; uint dnaModulus = 10 ** dnaDigits; uint cooldownTime = 1 days; struct Zombie { string name; uint dna; uint32 level; uint32 readyTime; } Zombie[] public zombies; mapping (uint => address) public zombieToOwner; mapping (address => uint) ownerZombieCount; function _createZombie(string _name, uint _dna) internal { uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime))) - 1; zombieToOwner[id] = msg.sender; ownerZombieCount[msg.sender]++; NewZombie(id, _name, _dna); } function _generateRandomDna(string _str) private view returns (uint) { uint rand = uint(keccak256(_str)); return rand % dnaModulus; } function createRandomZombie(string _name) public { require(ownerZombieCount[msg.sender] == 0); uint randDna = _generateRandomDna(_name); randDna = randDna - randDna % 100; _createZombie(_name, randDna); } } Ownable是来自 OpenZeppelin Solidity 库的 Ownable 合约。 事件 是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件,并做出反应。如: var abi = // abi是由编译器生成的 var ZombieFactoryContract = web3.eth.contract(abi) var contractAddress = /// 发布之后在以太坊上生成的合约地址 var ZombieFactory = ZombieFactoryContract.at(contractAddress) // ZombieFactory能访问公共的函数以及事件 // 监听NewZombie事件, 并且更新UI var event = ZombieFactory.NewZombie(function(error, result) { if (error) return generateZombie(result.zombieId, result.name, result.dna) })
ZombieFeeding.sol pragma solidity ^0.4.19; import "./zombiefactory.sol"; contract KittyInterface { function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes ); } contract ZombieFeeding is ZombieFactory { KittyInterface kittyContract; modifier ownerOf(uint _zombieId) { require(msg.sender == zombieToOwner[_zombieId]); _; } function setKittyContractAddress(address _address) external onlyOwner { kittyContract = KittyInterface(_address); } function _triggerCooldown(Zombie storage _zombie) internal { _zombie.readyTime = uint32(now + cooldownTime); } function _isReady(Zombie storage _zombie) internal view returns (bool) { return (_zombie.readyTime <= now); } function feedAndMultiply(uint _zombieId, uint _targetDna, string _species) internal ownerOf(_zombieId) { Zombie storage myZombie = zombies[_zombieId]; require(_isReady(myZombie)); _targetDna = _targetDna % dnaModulus; uint newDna = (myZombie.dna + _targetDna) / 2; if (keccak256(_species) == keccak256("kitty")) { newDna = newDna - newDna % 100 + 99; } _createZombie("NoName", newDna); _triggerCooldown(myZombie); } function feedOnKitty(uint _zombieId, uint _kittyId) public { uint kittyDna; (,,,,,,,,,kittyDna) = kittyContract.getKitty(_kittyId); feedAndMultiply(_zombieId, kittyDna, "kitty"); } } 在 Solidity 中,有一些全局变量可以被所有函数调用。 其中一个就是 msg.sender,它指的是当前调用者(或智能合约)的 address。
Solidity 使用自己的本地时间单位。 变量 now 将返回当前的unix时间戳(自1970年1月1日以来经过的秒数)。我写这句话时 unix 时间是 1515527488。
注意:Unix时间传统用一个32位的整数进行存储。这会导致“2038年”问题,当这个32位的unix时间戳不够用,产生溢出,使用这个时间的遗留系统就麻烦了。所以,如果我们想让我们的 DApp 跑够20年,我们可以使用64位整数表示时间,但为此我们的用户又得支付更多的 gas。真是个两难的设计啊!
Solidity 还包含秒(seconds),分钟(minutes),小时(hours),天(days),周(weeks) 和 年(years) 等时间单位。它们都会转换成对应的秒数放入 uint 中。所以 1分钟 就是 60,1小时是 3600(60秒×60分钟),1天是86400(24小时×60分钟×60秒),以此类推。
ZombieHelper.sol pragma solidity ^0.4.19; import "./zombiefeeding.sol"; contract ZombieHelper is ZombieFeeding { uint levelUpFee = 0.001 ether; modifier aboveLevel(uint _level, uint _zombieId) { require(zombies[_zombieId].level >= _level); _; } function withdraw() external onlyOwner { owner.transfer(this.balance); } function setLevelUpFee(uint _fee) external onlyOwner { levelUpFee = _fee; } function levelUp(uint _zombieId) external payable { require(msg.value == levelUpFee); zombies[_zombieId].level++; } function changeName(uint _zombieId, string _newName) external aboveLevel(2, _zombieId) ownerOf(_zombieId) { zombies[_zombieId].name = _newName; } function changeDna(uint _zombieId, uint _newDna) external aboveLevel(20, _zombieId) ownerOf(_zombieId) { zombies[_zombieId].dna = _newDna; } function getZombiesByOwner(address _owner) external view returns(uint[]) { uint[] memory result = new uint[](ownerZombieCount[_owner]); uint counter = 0; for (uint i = 0; i < zombies.length; i++) { if (zombieToOwner[i] == _owner) { result[counter] = i; counter++; } } return result; } } msg.value 是一种可以查看向合约发送了多少以太的方法,另外 ether 是一个內建单元。
这里发生的事是,一些人会从 web3.js 调用这个函数 (从DApp的前端), 像这样 : // 假设 `OnlineStore` 在以太坊上指向你的合约: OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))
ZombieBattle.sol pragma solidity ^0.4.19; import "./zombiehelper.sol"; contract ZombieBattle is ZombieHelper { uint randNonce = 0; uint attackVictoryProbability = 70; function randMod(uint _modulus) internal returns(uint) { randNonce++; return uint(keccak256(now, msg.sender, randNonce)) % _modulus; } function attack(uint _zombieId, uint _targetId) external ownerOf(_zombieId) { Zombie storage myZombie = zombies[_zombieId]; Zombie storage enemyZombie = zombies[_targetId]; uint rand = randMod(100); if (rand <= attackVictoryProbability) { myZombie.winCount++; myZombie.level++; enemyZombie.lossCount++; feedAndMultiply(_zombieId, enemyZombie.dna, "zombie"); } else { myZombie.lossCount++; enemyZombie.winCount++; _triggerCooldown(myZombie); } } }
区块链
2018-05-03 15:54:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
区块链兄弟社区,区块链技术专业问答先行者,中国区块链技术爱好者聚集地
作者:于中阳
来源: 区块链兄弟
原文链接: http://www.blockchainbrother.com/article/72
著权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1.发送者和接收者
发送者 (sender)即是发送信息消息的人,其想发送消息给 接收者 (receiver),接收者即是接收信息消息的人。在发送过程中,发送者和合法接收者都希望安全的发送及接收信息消息,并且需要确信第三方(窃听者)不能截取或阅读发送的信息消息。
2.消息和加密
消息(message)即为 明文 (plaintext)。用某种特定的方式方法对其进行“伪装”处理,以隐藏其的内容的过程,我们称之为 加密 (encryption)。而被加密之后的消息,我们称之为 密文 (ciphertext)。当我们将密文转变为明文,这一过程我们称之为 解密 (decryption)。
这一过程简单来说就是:“明文”>>(加密)>>“密文”>>(解密)>>“原始明文”。
在这一过程当中,使消息保密的技术被称为 密码编码学 (cryptography),从事这一工作的人,我们称之为 密码编码者 (cryptographer)。与此对立相反的就是破译密文的技术,我们称之为 密码分析学 (cryptanalysis),从事这一工作的人即对密码进行分析处理的专业人员,我们称之为 密码分析者 (cryptanalyst)。
而 密码学 (cryptology)包括了密码编码学和密码分析学两个部分,所以同时精于此二者的人,我们称之为 密码学家 (cryptologist)。由于密码学是作为数学的一个分支,现代的密码学家通常也是理论数学家。
注:密码学和数学有着千丝万缕的联系,学习密码学的同时也需要学习数学知识。
明文我们一般用M或P表示,明文可以是位序列、位图、文本文件、数字化的语音序列或数字化的视频图像等等。对于计算机而言,M一般仅简单指二进制数据。明文可以被传送或存储,无论哪种情况,M指待加密的信息消息。
密文我们用C表示,其也是二进制数据,有时和M一样大,有时比M大,但通过压缩和加密的结合,C同样有可能比M小。现我假定加密函数为E,那么E作用于M得到C的过程,可以用数学公式表示:
E(M)=C
相反的,若假定解密函数为D,解密函数D作用于C产生M的过程可以表示为:
D(C)=M
先进行加密操作,后进行解密操作,原始的明文将得以恢复,故有以下等式成立:
D( E(M) )=M
3.密码学的作用
密码学通常的作用是提供机密性,但除此之外,密码学还有以下作用。
鉴别 (authentication)消息的接收者应该能够确认消息的来源,第三方入侵者不可能伪装成合法的发送者和接收者。
完整性 (integrity)信息消息的接收者应该能够验证信息消息在传送过程中没有被篡改,第三方入侵者不可能用虚假的信息消息代替合法的信息消息。
抗抵赖 (nonrepudiation)信息消息的发送者事后不可能单方面虚假的否认是其发送了信息消息。
这些基本的功能都是通过计算机进行社会交流与协作至关重要的需求。现代社会已经变得离不开计算机,计算机也在人类社会的生产建设以及信息交互中扮演了极其重要的角色,以上密码学的基本作用保证了合法用户身份证明,信息消息真实性等,就像面对面的信息验证一样。
文章发布只为分享区块链技术内容,版权归原作者所有,观点仅代表作者本人,绝不代表区块链兄弟赞同其观点或证实其描述
区块链
2018-05-03 13:54:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
设计模式是许多开发场景中的首选解决方案,本文将介绍五种经典的智能合约设计模式并给出 以太坊solidity实现代码:自毁合约、工厂合约、名称注册表、映射表迭代器和提款模式。 如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 去中心化电商DApp实战开发
1、自毁合约
合约自毁模式用于终止一个合约,这意味着将从区块链上永久删除这个合约。 一旦被销毁,就不可能 调用合约的功能,也不会在账本中记录交易。
现在的问题是:“为什么我要销毁合约?”。
有很多原因,比如某些定时合约,或者那些一旦达到里程碑就必须终止的合约。 一个典型的案例 是贷款合约,它应当在贷款还清后自动销毁;另一个案例是基于时间的拍卖合约,它应当在拍卖结束后 终止 —— 假设我们不需要在链上保存拍卖的历史记录。
在处理一个被销毁的合约时,有一些需要注意的问题: 合约销毁后,发送给该合约的交易将失败 任何发送给被销毁合约的资金,都将永远丢失
为避免资金损失,应当在发送资金前确保目标合约仍然存在,移除所有对已销毁合约的引用。 现在我们来看看代码: contract SelfDesctructionContract { public address owner; public string someValue; modifier ownerRestricted { require(owner == msg.sender); _; } // constructor function SelfDesctructionContract() { owner = msg.sender; } // a simple setter function function setSomeValue(string value){ someValue = value; } // you can call it anything you want function destroyContract() ownerRestricted { suicide(owner); } }
正如你所看到的, destroyContract() 方法负责销毁合约。
请注意,我们使用自定义的 ownerRestricted 修饰符来显示该方法的调用者,即仅允许合约的拥有者 销毁合约。
2、工厂合约
工厂合约用于创建和部署“子”合约。 这些子合约可以被称为“资产”,可以表示现实生活中的房子或汽车。
工厂用于存储子合约的地址,以便在必要时提取使用。 你可能会问,为什么不把它们存在Web应用数据库里? 这是因为将这些地址数据存在工厂合约里,就意味着是存在区块链上,因此更加安全,而数据库的损坏 可能会造成资产地址的丢失,从而导致丢失对这些资产合约的引用。 除此之外,你还需要跟踪所有新 创建的子合约以便同步更新数据库。
工厂合约的一个常见用例是销售资产并跟踪这些资产(例如,谁是资产的所有者)。 需要向负责部署资产的 函数添加payable修饰符以便销售资产。 代码如下: contract CarShop { address[] carAssets; function createChildContract(string brand, string model) public payable { // insert check if the sent ether is enough to cover the car asset ... address newCarAsset = new CarAsset(brand, model, msg.sender); carAssets.push(newCarAsset); } function getDeployedChildContracts() public view returns (address[]) { return carAssets; } } contract CarAsset { string public brand; string public model; address public owner; function CarAsset(string _brand, string _model, address _owner) public { brand = _brand; model = _model; owner = _owner; } }
代码 address newCarAsset = new CarAsset(...) 将触发一个交易来部署子合约并返回该合约的地址。 由于工厂合约和资产合约之间唯一的联系是变量 address[] carAssets ,所以一定要正确保存子合约的地址。
3、名称注册表
假设你正在构建一个依赖与多个合约的DApp,例如一个基于区块链的在线商城,这个DApp使用了 ClothesFactoryContract、GamesFactoryContract、BooksFactoryContract等多个合约。
现在想象一下,将所有这些合约的地址写在你的应用代码中。 如果这些合约的地址随着时间的推移而变化,那该怎么办?
这就是名称注册表的作用,这个模式允许你只在代码中固定一个合约的地址,而不是数十、数百甚至数千个 地址。它的原理是使用一个合约名称 => 合约地址的映射表,因此可以通过调用 getAddress("ClothesFactory") 从DApp内查找每个合约的地址。 使用名称注册表的好处是,即使更新那些合约,DApp也不会受到任何影响,因为 我们只需要修改映射表中合约的地址。
代码如下: contract NameRegistry { struct ContractDetails { address owner; address contractAddress; uint16 version; } mapping(string => ContractDetails) registry; function registerName(string name, address addr, uint16 ver) returns (bool) { // versions should start from 1 require(ver >= 1); ContractDetails memory info = registry[name]; require(info.owner == msg.sender); // create info if it doesn't exist in the registry if (info.contractAddress == address(0)) { info = ContractDetails({ owner: msg.sender, contractAddress: addr, version: ver }); } else { info.version = ver; info.contractAddress = addr; } // update record in the registry registry[name] = info; return true; } function getContractDetails(string name) constant returns(address, uint16) { return (registry[name].contractAddress, registry[name].version); } }
你的DApp将使用 getContractDetails(name) 来获取指定合约的地址和版本。
4、映射表迭代器
很多时候我们需要对一个映射表进行迭代操作 ,但由于Solidity中的映射表只能存储值, 并不支持迭代,因此映射表迭代器模式非常有用。 需要指出的是,随着成员数量的增加, 迭代操作的复杂性会增加,存储成本也会增加,因此请尽可能地避免迭代。
实现代码如下: contract MappingIterator { mapping(string => address) elements; string[] keys; function put(string key, address addr) returns (bool) { bool exists = elements[key] == address(0) if (!exists) { keys.push(key); } elements[key] = addr; return true; } function getKeyCount() constant returns (uint) { return keys.length; } function getElementAtIndex(uint index) returns (address) { return elements[keys[index]]; } function getElement(string name) returns (address) { return elements[name]; } }
实现 put() 函数的一个常见错误,是通过遍历来检查指定的键是否存在。正确的做法是 elements[key] == address(0) 。虽然遍历检查的做法不完全是一个错误,但它并不可取, 因为随着keys数组的增长,迭代成本越来越高,因此应该尽可能避免迭代。
5、提款模式
假设你销售汽车轮胎,不幸的是卖出的所有轮胎出问题了,于是你决定向所有的买家退款。
假设你跟踪记录了合约中的所有买家,并且合约有一个refund()函数,该函数会遍历所有买家 并将钱一一返还。
你可以选择 - 使用buyerAddress.transfer()或buyerAddress.send() 。 这两个函数的区别在于, 在交易异常时,send()不会抛出异常,而只是返回布尔值false ,而transfer()则会抛出异常。
为什么这一点很重要?
假设大多数买家是外部账户(即个人),但一些买家是其他合约(也许是商业)。 假设在 这些买方合约中,有一个合约,其开发者在其fallback函数中犯了一个错误,并且在被调用时抛出一个异常, fallback()函数是合约中的默认函数,如果将交易发送到合同但没有指定任何方法,将调用合约 的fallback()函数。 现在,只要我们在refund函数中调用contractWithError.transfer() ,就会抛出 异常并停止迭代遍历。 因此,任何一个买家合约的fallback()异常都将导致整个退款交易被回滚, 导致没有一个买家可以得到退款。
虽然在一次调用中退款所有买家可以使用send()来实现,但是更好的方式是提供withdrawFunds()方法,它 将单独按需要退款给调用者。 因此,错误的合约不会应用其他买家拿到退款。
实现代码如下: contract WithdrawalContract { mapping(address => uint) buyers; function buy() payable { require(msg.value > 0); buyers[msg.sender] = msg.value; } function withdraw() { uint amount = buyers[msg.sender]; require(amount > 0); buyers[msg.sender] = 0; require(msg.sender.send(amount)); } }
原文: 5种经典的智能合约设计模式
区块链
2018-05-03 09:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
1、初识以太坊
1、课程概述
本课程面向初学者,内容涵盖以太坊开发相关的基本概念,并将手把手地教大家如何构建一个 基于以太坊的完整去中心化应用 —— 区块链投票系统。
通过本课程的学习,你将掌握: 以太坊区块链的基本知识 开发和部署以太坊合约所需的软件环境 使用高级语言( solidity )编写以太坊合约 使用NodeJS编译、部署合约并与之交互 使用 Truffle 框架开发分布式应用 使用控制台或网页与合约进行交互
前序知识要求
为了顺利完成本课程,最好对以下技术已经有一些基本了解: 一种面向对象的开发语言,例如:Python,Ruby,Java... 前端开发语言:HTML/CSS/JavaScript Linxu命令行的使用 数据库的基本概念
课程的所有代码均已在Ubuntu(Trusty、Xenial)和 macOS 上测试过。
2、课程项目简介
在本课程中,我们将会构建一个去中心化的( Decentralized )投票应用。利用这个投票应用, 用户可以在不可信( trustless )的分布环境中对特定候选人投票,每次投票都会被记录在区块 链上:
所谓去中心化应用( DApp :Dcentralized Application),就是一个不存在中心服务器 的应用。在网络中成百上千的电脑上,都可以运行该应用的副本,这使得它几乎不可能 出现宕机的情况。
基于区块链的投票是完全去中心化的,因此无须任何中心化机构的存在。
思考一下,以下应用是不是去中心化应用:QQ、电驴、迅雷、加密币交易所?
3、开发迭代
本课程将涵盖应用开发的整个过程,我们将通过三次迭代来渐进地引入区块链应用 开发所涉及的相关概念、语言和工具:
Vanilla :在第一个迭代周期,我们不借助任何开发框架,而仅仅使用NodeJS来进行应用开发, 这有助于我们更好地理解区块链应用的核心理念。 Truffle :在第二个迭代周期,我们将使用最流行的去中心化应用开发框架 Truffle 进行开发。 使用开发框架有助于我们提高开发效率。 Token :在第三个迭代周期,我们将为投票应用引入代币( Token ) —— 现在大家都改口 称之为 通证 了 —— 都是 ICO 惹的祸。代币是公链上不可或缺的激励机制,也是区块链 应用区别于传统的中心化应用的另一个显著特征。
为什么选择投票应用作为课程项目?
之所以选择投票作为我们的第一个区块链应用,是因为集体决策 —— 尤其是投票机制 —— 是以太坊的 一个核心的价值主张。
另一个原因在于,投票是很多复杂的去中心化应用的基础构件,所以我们选择了投票应用作为学习区块链 应用开发的第一个项目。
4、初识区块链
如果你熟悉关系型数据库,就应该知道一张数据表里可以包含很多行数据记录。例如,下面的数据表中 包含了6条交易记录:
本质上,区块链首先就是一个分布式( Distributed )数据库,这个数据库维护了一个 不断增长 的记录列表。 现在,让我们对数据进行批量( batch )存储,比如每批 100 行,并将各存储批次连接起来,是不是就像一条链?
在区块链里,多个数据记录组成的批次就被称为块( block ),块里的每一行数据记录就被称为交易( transaction ):
最开始的那个块,通常被称为创世块( genesis block ),它不指向任何其他块。
不可篡改性
区块链的一个显著特点是,数据一旦写入链中,就不可篡改重写。
在传统的关系型数据库中,你可以很容易地更新一条数据记录。但是,在区块链中,一旦数据写入就无法 再更新了 —— 因此,区块链是一直增长的。
那么,区块链是如何实现数据的不可篡改特性?
这首先得益于哈希( Hash )函数 —— 如果你还没接触过哈希函数,不妨将它视为一个数字指纹的计算函数: 输入任意长度的内容,输出定长的码流(指纹)。哈希函数的一个重要特性就是,输入的任何一点微小变化,都会 导致输出的改变。因此可以将哈希值作为内容的指纹来使用。 你可以点击 这里 进一步了解哈希函数。
由于区块链里的每个块都存储有前一个块内容的哈希值,因此如果有任何块的内容被篡改,被篡改的块之后 所有块的哈希值也会随之改变,这样我们就很容易检测出区块链的各块是否被篡改了。
去中心化的挑战
一旦完全去中心化,在网络上就会存在大量的区块链副本(即:全节点),很多事情都会变得比之前中心化 应用环境复杂的多,例如: 如何保证所有副本都已同步到最新状态? 如何保证所有交易都被广播到所有运行和维护区块链副本的节点计算机上? 如何防止恶意参与者篡改区块链 ......
在接下来的课程中,通过与经典的C/S架构的对比,我们将逐步理解去中心化应用的核心思路, 并掌握如何构建以太坊上的去中心化应用。
阅读课程内容并思考,区块链采用了哪些机制来保证链上数据不被篡改?
5、C/S架构 —— 以服务器为中心
理解去中心化应用架构的最好方法,就是将它与熟悉的 Client/Server 架构进行对比。如果你是一个 web 开发者, 应该对下图很了解,这是一个典型的 Client/Server 架构:
一个典型web应用的服务端通常由 Java,Ruby,Python 等等语言实现。前端代码由 HTML/CSS/JavaScript 实现。 然后将整个应用托管在云端,比如 AWS、Google Cloud Platform、Heroku....,或者放在你租用的一个 VPS 主机上。
用户通过客户端( Client )与 web 应用( Server )进行交互。典型的客户端包括浏览器、命令行工具( curl 、 wget 等)、 或者是 API 访问代码。注意在这种架构中,总是存在一个(或一组)中心化的 web 服务器,所有的客户端都需要 与这一(组)服务器进行交互。当一个客户端向服务器发出请求时,服务器处理该请求,与数据库/缓存进行交互, 读/写/更新数据库,然后向客户端返回响应。
这是我们熟悉的中心化架构。在下一节,我们将会看到基于区块链的去中心化架构的一些显著区别。
思考一下,是不是所有的C/S架构的应用都是中心化应用?
6、去中心化架构 —— 彼此平等的节点
下图给出了基于以太坊的去中心化应用架构:
你应该已经注意到,每个客户端(浏览器)都是与各自的节点应用实例进行交互,而不是向 一个中心化的服务器请求服务。
在一个理想的去中心化环境中,每个想要跟DApp交互的人,都需要在他们的计算机或手机上面运行 一个的完整区块链节点 —— 简言之,每个人都运行一个 全节点 。这意味着,在能够真正使用一个 去中心化应用之前,用户不得不下载 整个 区块链。
不过我们并非生活在一个乌托邦里,期待每个用户都先运行一个全节点,然后再使用你的应用是不现实的。 但是去中心化背后的核心思想,就是 不依赖于中心化的服务器 。所以,区块链社区已经出现了 一些解决方案,例如提供公共区块链节点的 Infura , 以及浏览器插件 Metamask 等。通过这些方案, 你就不需要花费大量的硬盘、内存和时间去下载并运行完整的区块链节点,同时也可以利用去中心化 的优点。我们将会以后的课程中对这些解决方案分别进行评测。
思考一下,为什么在每个区块链节点上都需要保存全部的数据?
7、以太坊 —— 世界计算机
以太坊是一种区块链的实现。在以太坊网络中,众多的节点彼此连接,构成了以太坊网络:
以太坊节点软件提供两个核心功能:数据存储、合约代码执行。
在每个以太坊全节点中,都保存有完整的区块链数据。以太坊不仅将交易数据保存在链上,编译后 的合约代码同样也保存在链上。
以太坊全节点中,同时还提供了一个虚拟机来执行合约代码。
交易数据
以太坊中每笔交易都存储在区块链上。当你部署合约时,一次部署就是一笔交易。当你为候选者投票时,一次投票 又是另一笔交易。所有的这些交易都是公开的,每个人都可以看到并进行验证。这个数据永远也无法篡改。
为了确保网络中的所有节点都有着同一份数据拷贝,并且没有向数据库中写入任何无效数据,以太坊 目前使用 工作量证明 ( POW:Proof Of Work )算法来保证网络安全,即通过矿工挖矿( Mining )来达成共识( Consensus )—— 将数据同步到所有节点。
工作量证明不是达成共识的唯一算法,挖矿也不是区块链的唯一选择。现在,我们只需要了解,共识是指各节点 的数据实现了一致, POW 只是众多用于建立共识的算法中的一种,这种算法需要通过矿工的挖矿来实现非可信环境下的 可信交易。共识是目的,POW是手段。
合约代码
以太坊不仅仅在链上存储交易数据,它还可以在链上存储合约代码。
在数据库层面,区块链的作用就是存储交易数据。那么给候选者投票、或者检索投票结果的逻辑放在哪儿呢? 在以太坊的世界里,你可以使用 Solidity 语言来编写业务逻辑/应用代码(也就是合约: Contract ), 然后将合约代码编译为以太坊字节码,并将字节码部署到区块链上:
编写合约代码也可以使用其他的语言,不过 Solidity 是到目前为止最流行的选择。
以太坊虚拟机
以太坊区块链不仅存储数据和代码,每个节点中还包含一个虚拟机(EVM:Ethereum Virtual Machine)来执行 合约代码 —— 听起来就像计算机操作系统。
事实上,这一点是以太坊区别于比特币( Bitcoin )的最核心的一点:虚拟机的存在使区块链迈入了2.0 时代,也让区块链第一次成为应用开发者友好的平台。
JS开发库
为了便于构建基于web的DApp,以太坊还提供了一个非常方便的JavaScript库 web3.js ,它封装了以太坊节点的API 协议,从而让开发者可以轻松地连接到区块链节点而不必编写繁琐的 RPC 协议包。所以,我们可以在常用的JS框架 (比如 reactjs、angularjs 等)中直接引入该库来构建去中心化应用:
阅读教程,回答以下问题: 共识是什么意思?它和工作量证明是什么关系? 合约是保存在链上吗?
2、使用NodeJS开发DApp
1、开发流程概述
现在,在初步了解以太坊的基本概念之后,我们将开始构建投票DApp。通过实际应用开发,有助于我们加深 对以太坊的认识,并初步了解以太坊所提供的功能。
下图展示了应用的整体结构:

从图中可以看到,网页通过(HTTP上的)远程过程调用(PRC:Remote Procedure Call)与区块链节点进行通信。 web3.js 已经封装了以太坊规定的全部 RPC 调用,因此利用它就可以与区块链进行交互,而不必手写那些RPC请求包。 使用 web3.js 的另一个好处是,你可以使用自己喜欢的前端框架来构建出色的web 应用。
由于获得一个同步的全节点相当耗时,并占用大量磁盘空间。为了在我们对区块链的兴趣消失之前掌握 如何开发一个去中心化应用,本课程将使用 ganache 软件来模拟区块链节点,以便快速开发并测试应用, 从而可以将注意力集中在去中心化的思想理解与DApp应用逻辑开发方面。
接下来,我们将编写一个投票合约,然后编译合约并将其部署到区块链节点 —— ganache上。
最后,我们将分别通过命令行和网页这两种方式,与区块链进行交互。
2、节点仿真器
课程环境已经预置了 ganache 软件。在终端中执行下面命令来启动它: ~$ ganache-cli
ganache 将输出如下信息: Ganache CLI v6.0.3 (ganache-core: 2.0.2) Available Accounts ================== (0) 0x5c252a0c0475f9711b56ab160a1999729eccce97 (1) 0x353d310bed379b2d1df3b727645e200997016ba3 (2) 0xa3ddc09b5e49d654a43e161cae3f865261cabd23 (3) 0xa8a188c6d97ec8cf905cc1dd1cd318e887249ec5 (4) 0xc0aa5f8b79db71335dacc7cd116f357d7ecd2798 (5) 0xda695959ff85f0581ca924e549567390a0034058 (6) 0xd4ee63452555a87048dcfe2a039208d113323790 (7) 0xc60c8a7b752d38e35e0359e25a2e0f6692b10d14 (8) 0xba7ec95286334e8634e89760fab8d2ec1226bf42 (9) 0x208e02303fe29be3698732e92ca32b88d80a2d36 Private Keys ================== (0) a6de9563d3db157ed9926a993559dc177be74a23fd88ff5776ff0505d21fed2b (1) 17f71d31360fbafbc90cad906723430e9694daed3c24e1e9e186b4e3ccf4d603 (2) ad2b90ce116945c11eaf081f60976d5d1d52f721e659887fcebce5c81ee6ce99 (3) 68e2288df55cbc3a13a2953508c8e0457e1e71cd8ae62f0c78c3a5c929f35430 (4) 9753b05bd606e2ffc65a190420524f2efc8b16edb8489e734a607f589f0b67a8 (5) 6e8e8c468cf75fd4de0406a1a32819036b9fa64163e8be5bb6f7914ac71251cc (6) c287c82e2040d271b9a4e071190715d40c0b861eb248d5a671874f3ca6d978a9 (7) cec41ef9ccf6cb3007c759bf3fce8ca485239af1092065aa52b703fd04803c9d (8) c890580206f0bbea67542246d09ab4bef7eeaa22c3448dcb7253ac2414a5362a (9) eb8841a5ae34ff3f4248586e73fcb274a7f5dd2dc07b352d2c4b71132b3c73f0 HD Wallet ================== Mnemonic: cancel better shock lady capable main crunch alcohol derive alarm duck umbrella Base HD Path: m/44'/60'/0'/0/{account_index} Listening on localhost:8545
为了便于开发与测试,ganache 默认会自动创建 10 个账户,每个账户有 100 个以太币(ETH:Ether)。 可以把账户视为银行账户,以太币就是以太坊生态系统中的货币。
例如,在上面的输出中,可以看到第一个账户是
0x5c252a0c0475f9711b56ab160a1999729eccce97,启动时,该账户中预置有100ETH的余额(Balance)。
接下来我们将使用这个账户创建交易、发送/接收以太币。
上面输出的最后一句话,描述了节点仿真器的监听地址和端口为 localhost:8545 ,在使用 web3.js 时,需要传入这个地址来告诉 web3js 库应当连接到哪一个节点。
当你在自己的计算机上练习时,也可以安装 GUI 版本的 ganache,下载地址: ganache-gui 。
思考与练习: 在第一个终端窗口中输入以下命令查看 ganache-cli 的可选参数: ganache-cli --help 。 使用默认参数启动 ganache-cli ,查看屏幕输出,找出账户列表。
3、投票合约设计
了解如何运行节点仿真器之后,可以开始设计我们的第一个合约了。
我们使用 Solidity 语言来编写合约。如果你熟悉面向对象的开发和 JavaScript ,那么学习 Solidity 应该非常简单。可以将合约类比于 OOP 的类:合约中的属性用来声明合约的状态,而合约中的方法则提 供修改状态的访问接口。下图给出了投票合约的主要接口:

基本上,投票合约 Voting 包含以下内容: 构造函数,用来初始化候选人名单。 投票方法 Vote() ,每次执行就将指定的候选人得票数加 1 得票查询方法 totalVotesFor() ,执行后将返回指定候选人的得票数
有两点需要特别指出: 合约状态是持久化到区块链上的,因此对合约状态的修改需要消耗以太币。 只有在合约部署到区块链的时候,才会调用构造函数,并且只调用一次。 与 web 世界里每次部署代码都会覆盖旧代码不同,在区块链上部署的合约是不可改变的,也就是说,如果你更新 合约并再次部署,旧的合约仍然会在区块链上存在,并且合约的状态数据也依然存在。新的部署将会创建合约的一 个新的实例。
Solidity 语言的详细介绍可以在 这里 找到。
思考与练习,阅读教程,回答以下问题: 合约的构造函数和面向对象开发中的类的构造函数有什么不同之处? 一个实例化合约的属性和一个类的实例化对象的属性有什么不同之处?
4、合约代码开发
投票合约的代码保存在 ~/repo/chapter1/Voting.sol 文件中,可以使用实验环境中的编辑器 打开查看其内容。
Voting.sol 代码如下: pragma solidity ^0.4.18; contract Voting { mapping (bytes32 => uint8) public votesReceived; bytes32[] public candidateList; function Voting(bytes32[] candidateNames) public { candidateList = candidateNames; } function totalVotesFor(bytes32 candidate) view public returns (uint8) { require(validCandidate(candidate)); return votesReceived[candidate]; } function voteForCandidate(bytes32 candidate) public { require(validCandidate(candidate)); votesReceived[candidate] += 1; } function validCandidate(bytes32 candidate) view public returns (bool) { for(uint i = 0; i < candidateList.length; i++) { if (candidateList[i] == candidate) { return true; } } return false; } }
编译器要求 pragma solidity ^0.4.18;
声明合约代码的编译器版本要求。 ^0.4.18 表示要求合约编译器版本不低于 0.4.18 。
合约声明 contract Voting{}
contract 关键字用来声明一个合约。
字典类型:mapping mapping (bytes32 => uint8) public votesReceived;
mapping 可以类比于一个关联数组或者是字典,是一个键值对。例如, votesReceived 状态的 键是候选者的名字,类型为 bytes32 —— 32个字节定长字符串。 votesReceived 状态中每个键对应的值 是一个单字节无符号整数( uint8 ),用来存储该候选人的得票数:
bytes32[] public candidateList;
在JS中,使用 votesReceived.keys 就可以获取所有的候选人姓名。但是在 Solidity 中 没有这样的方法,所以我们需要单独管理全部候选人的名称 —— candidateList 数组。 function voteForCandidate(bytes32 candidate) public { require(validCandidate(candidate)); votesReceived[candidate] += 1; }
在 voteForCandidate() 方法中,请注意 votesReceived[key] 有默认值 0,所以我们没有进行初始化, 而是直接加1。
在合约方法体内的 require() 语句类似于断言,只有条件为真时,合约才继续执行。 validateCandidate() 方法只有在给定的候选人名称在部署合约时传入的候选人名单中时才返回真值,从而避免乱投票的行为: function validCandidate(bytes32 candidate) view public returns (bool) { for(uint i = 0; i < candidateList.length; i++) { if (candidateList[i] == candidate) { return true; } } return false; }
方法声明符与修饰符
在 Solidity 中,可以为函数应用可视性声明符( visibility specifier ),例如 public 、 private 。 public 意味着可以从合约外调用函数。如果一个方法仅限合约内部调用,可以把它声明为私有( private )。 点击 这里 可以查看所有的可视性说明符。
在 Solidity 中,还可以为函数声明修饰符( modifier ),例如 view 用来告诉编译器,这个函数是只读的,也就是说, 该函数的执行不会改变区块链的状态)。所有的修饰符都可以在 这里 看到。
思考与练习:
当前合约允许一个人多次投票。请修改合约来保证一个人只能投一次票。
5、合约代码编译
我们使用 solc 库来编译合约代码。如果你还记得的话,之前我们提到过 web3js 库, 它能够让你通过 RPC 与区块链进行交互。我们将在 node 控制台里用这个库编译和部署合约, 并与区块链进行交互。
首先,请确保 ganache 已经在第一个终端窗口中运行: ~$ ganache-cli 。
然后,在另一个终端中进入 repo/chapter1 目录,启动node 控制台,然后初始化 web3 对象,并向本地区块 链节点( http://localhost:8545 )查询获取所有的账户: ~$ cd ~/repo/chapter1 ~/repo/chapter1$ node > Web3 = require('web3') > web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); > web3.eth.accounts ['0x5c252a0c0475f9711b56ab160a1999729eccce97' '0x353d310bed379b2d1df3b727645e200997016ba3' '0xa3ddc09b5e49d654a43e161cae3f865261cabd23' '0xa8a188c6d97ec8cf905cc1dd1cd318e887249ec5' '0xc0aa5f8b79db71335dacc7cd116f357d7ecd2798' '0xda695959ff85f0581ca924e549567390a0034058' '0xd4ee63452555a87048dcfe2a039208d113323790' '0xc60c8a7b752d38e35e0359e25a2e0f6692b10d14' '0xba7ec95286334e8634e89760fab8d2ec1226bf42' '0x208e02303fe29be3698732e92ca32b88d80a2d36']
要编译合约,首先需要载入 Voting.sol 文件的内容,然后使用编译器( solc )的 compile() 方法 对合约代码进行编译: > code = fs.readFileSync('Voting.sol').toString() > solc = require('solc') > compiledCode = solc.compile(code)
成功编译合约后可以查看一下编译结果。直接在控制台输入: > compiledCode
相当长一大段输出霸屏...
便以结果是一个JSON对象,其中包含两个重要的字段: compiledCode.contracts[':Voting'].bytecode: 投票合约编译后的字节码,也是要部署到区块链上的代码。 compiledCode.contracts[':Voting'].interface: 投票合约的接口,被称为应用二进制接口( ABI:Application Binary Interface ), 它声明了合约中包含的接口方法。无论何时需要跟一个合约进行交互,都需要该合约的 abi 定义。你可以在 这里 查看ABI的详细信息。
在接下来的几节课,我们将会使用 truffle 框架来管理合约的编译过程以及与区块链的交互过程。但是, 在使用框架之前,深入了解其工作原理还是大有裨益的,因为框架会将这些脏活封装起来,在出现 故障时并不容易排查错误。
思考与练习:
阅读教程,启动 ganache-cli ,编译投票合约并查看其ABI定义和字节码。
6、投票合约部署
让我们继续课程,现在将投票合约部署到区块链上。
为此,需要先传入合约的 abi 定义来创建合约对象 VotingContract ,然后利用该对象完成合约在链上的部署和初始化。
在node控制台执行以下命令: > abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface) > VotingContract = web3.eth.contract(abiDefinition) > byteCode = compiledCode.contracts[':Voting'].bytecode > deployedContract = VotingContract.new(['Rama','Nick','Jose'],{data: byteCode, from: web3.eth.accounts[0], gas: 4700000}) > deployedContract.address '0x0396d2b97871144f75ba9a9c8ae12bf6c019f610' <- 你的部署地址可能和这个不一样 > contractInstance = VotingContract.at(deployedContract.address)
调用 VotingContract 对象的 new() 方法来将投票合约部署到区块链。 new() 方法参数列表应当与合约的 构造函数要求相一致。对于投票合约而言, new() 方法的第一个参数是候选人名单。
new() 方法的最后一个参数用来声明部署选项。现在让我们来看一下这个参数的内容: { data: byteCode, //合约字节码 from: web3.eth.accounts[0], //部署者账户,将从这个账户扣除执行部署交易的开销 gas: 4700000 //愿意为本次部署最多支付多少油费,单位:Wei } data: 这是合约编译后,需要部署到区块链上的合约字节码。 from: 区块链必须跟踪是谁部署了一个合约。在本例中,我们简单地利用 web3.eth.accounts 返回的 第一个账户,作为部署这个合约的账户。在提交交易之前,你必须拥有并解锁这个账户。不过为了方便 起见, ganache 默认会自动解锁这10个账户。 gas: 与区块链进行交互需要消耗资金。这笔钱用来付给矿工,因为他们帮你把代码部署到在区块链里。你 必须声明愿意花费多少资金让你的代码包含在区块链中,也就是设定 “gas” 的值。“from”字段声明的账户的 余额将会被用来购买 gas。gas 的价格则由区块链网络设定。
我们已经成功部署了投票合约,并且获得了一个合约实例(变量 contractInstance ),现在可以用这个实例 与合约进行交互了。
在区块链上有上千个合约。那么,如何识别你的合约已经上链了呢?
答案是:使用 deployedContract.address 。 当你需要跟合约进行交互时,就需要这个部署地址和我们之前 谈到的 abi 定义。 因此, 请记住这个地址 。
思考与练习:
参考教程,将投票合约部署到区块链上。
7、控制台交互
让我们继续课程。
调用合约的 totalVotesFor() 方法来查看某个候选人的得票数。例如,下面的代码 查看候选人 Rama 的得票数: > contractInstance.totalVotesFor.call('Rama') { [String: '0'] s: 1, e: 0, c: [ 0 ] }
{ [String: '0'] s: 1, e: 0, c: [ 0 ] } 是数字 0 的科学计数法表示. 你可以在 这里 了解科学计数法的详细信息。
调用合约的 voteForCandidate() 方法投票给某个候选人。下面的代码给 Rama 投了三次票: > contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]}) '0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53' > contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]}) '0x02c054d238038d68b65d55770fabfca592a5cf6590229ab91bbe7cd72da46de9' > contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]}) '0x3da069a09577514f2baaa11bc3015a16edf26aad28dffbcd126bde2e71f2b76f'
现在我们再次查看 Rama 的得票数: > contractInstance.totalVotesFor.call('Rama').toLocaleString() '3'
投票 = 交易
每执行一次投票,就会产生一次交易,因此 voteForCandidate() 方法将返回一个交易id,作为 交易的凭据。比如: 0xdedc7ae544c3dde74ab5a0b07422c5a51b5240603d31074f5b75c0ebc786bf53 。 交易id是交易发生的凭据,交易是不可篡改的,因此任何时候可以使用交易id引用或查看交易内容 都会得到同样的结果。
对于区块链而言,交易不可篡改是其核心特性。在接下来的章节,我们将会利用这一特性来构建应用。
思考与练习:
参考教程,分别投票给Jose和Nick。
8、网页交互
至此,大部分的工作都已完成,接下来让我们创建一个简单的 html 页面,以便用户可以使用浏览器 而不是复杂的命令行来与投票合约交互:

页面的主要功能如下: 列出所有的候选人及其得票数 用户在页面中可以输入候选人的名称,然后点击投票按钮,网页中的JS代码将调用投票合约的 voteForCandidate() 方法 —— 和我们nodejs控制台里的流程一样。
你可以在实验环境编辑器中打开 ~/repo/chapter1/index.html 来查看页面源代码。 为了聚焦核心业务逻辑,我们在网页中硬编码了候选人姓名。如果你喜欢的话,可以调整代码来动态生成候选人。
index.html 代码如下: Hello World DApp

A Simple Hello World Voting Application

Candidate Votes
Rama
Nick
Jose
Vote
页面文件中的JS代码都封装在了一个单独的JS文件中,可以在试验环境编辑器中打开 ~/repo/chapter1/index.js 来查看其内容。
index.js 代码如下: web3 = new Web3(new Web3.providers.HttpProvider("http://8545.0bcc71cc8eb1ffc1fcd374acb10ccf06.x.hubwiz.com/")); abi = JSON.parse('[{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"x","type":"bytes32"}],"name":"bytes32ToString","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"contractOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"type":"constructor"}]') VotingContract = web3.eth.contract(abi); contractInstance = VotingContract.at('0x9a0036b01f999f8c046ea5fa7b5dddabe24ed8de'); candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"} function voteForCandidate(candidate) { candidateName = $("#candidate").val(); try { contractInstance.voteForCandidate(candidateName, {from: web3.eth.accounts[0]}, function() { let div_id = candidates[candidateName]; $("#"+div_id).html(contractInstance.totalVotesFor.call(candidateName).toString()); }); } catch (err) { } } $(document).ready(function() { candidateNames = Object.keys(candidates); for (var i = 0; i < candidateNames.length; i++) { let name = candidateNames[i]; let val = contractInstance.totalVotesFor.call(name).toString() $("#"+candidates[name]).html(val); } });
为了将页面运行起来,需要根据你的私有试验环境对JS代码进行一下调整:
节点的RPC API地址 web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
HttpProvier() 对象的构造函数参数是web3js库需要链接的以太坊节点RPC API的URL,要调整为 你的私有试验环境中 ganache 的访问端结点,格式为: http://8545.<你的私有实验环境URL>/
查看试验环境中的嵌入浏览器地址栏来获取你的私有实验环境URL:
投票合约地址
当一个合约部署到区块链上时,将获得一个地址,例如 0x329f5c190380ebcf640a90d06eb1db2d68503a53 。 由于每次部署都会获得一个不同的地址,因此你需要指定它: contractInstance = VotingContract.at('0x329f5c190380ebcf640a90d06eb1db2d68503a53')
如果你在部署合约的时候没有记录这个地址,就重新部署吧。
运行web服务
在第二个终端中输入以下命令来启动一个简单的Web服务器,以便我们可以在试验环境中的嵌入浏览器中访问页面: ~$ cd ~/repo/chapter1 ~/repo/chapter1$ python -m SimpleHTTPServer
Python的 SimpleHTTPServer 模块将启动在8000端口的监听。
现在,在试验环境的嵌入浏览器中点击刷新按钮。如果一切顺利的话,你应该可以看到投票应用的页面了。 当你在文本框中输入候选人姓名,例如 Rama ,然后点击按钮后,应该会看到候选人 Rama 的得票数加 1 。
思考与练习:
参考教程,通过网页访问投票合约。
9、课程小结
如果你可以看到页面,当点击投票按钮后可以看到投票数增加,那你就已经成功创建了第一个去中心化应用,恭喜!
总结一下,下面是我们到目前为止已经完成的事情: 使用nodejs, npm 和 ganache作为开发环境。 开发简单的投票合约,编译并部署到区块链节点上。 使用nodejs 控制台与合约交互。 编写网页与合约交互。 所有的投票都保存到区块链上,并且不可修改。 任何人都可以独立验证每个候选人获得了多少投票。
在接下来的课程,我们将学习如何使用 Truffle 框架构建去中心化应用,也会更深入地学习 合约开发语言 Solidity 。
3、使用Truffle开发DApp
1、内容概述
在之前的课程中,我们已经基于区块链( ganache 仿真器)实现了一个投票合约,并且成功地 通过 nodejs 控制台和网页实现了与合约的交互。
在接下来的章节,我们将会实现以下内容: 使用 Truffle 框架开发投票应用,它可以方便地编译、部署合约。 修改已有的投票应用代码,以便适配开发框架。 利用 Truffle 控制台、网页与投票合约进行交互。 对投票合约进行扩展,加入通证( token )及购买功能。 对前端代码进行扩展,通过网页前端购买股票通证,并利用股票通证为候选人投票。
2、初始化项目
Truffle 是一个DApp开发框架,它简化了去中心化应用的构建和管理。你可以在 这里 了解框架的 更多内容和完整特性。
在实验环境中已经预置了 Truffle ,如果你需要在自己的机器上安装,可以使用 npm 全局安装: ~$ npm install -g truffle
Truffle提供了众多的项目模版,可以快速搭建一个去中心化应用的骨架代码。下面的代码 使用 webpack 项目模版来创建应用 tfapp : ~$ mkdir -p ~/repo/tfapp ~$ cd ~/repo/tfapp ~/repo/tfapp$ truffle unbox webpack
初始化一个Truffle项目时,它会创建运行一个完整的DApp所需的文件和目录。 可以使用 ls 命令来查看生成的项目结构: ~/repo/tfapp$ ls README.md contracts node_modules test webpack.config.js truffle.js app migrations package.json ~/repo/tfapp$ ls app/ index.html javascripts stylesheets ~/repo/tfapp$ ls contracts/ ConvertLib.sol MetaCoin.sol Migrations.sol ~/repo/tfapp$ ls migrations/ 1_initial_migration.js 2_deploy_contracts.js
由于不需要所生成的示例应用中的合约,因此可以放心地删除 contracts 目录中的 除 Migrations.sol 之外的其他合约文件: ~/repo/tfapp$ rm contracts/ConvertLib.sol contracts/MetaCoin.sol
Migrations.sol 合约用来管理应用合约的部署,因此请勿删除。
思考与练习:
参考教程,在 repo 目录下创建新项目 tfapp 。
3、升级投票应用代码

在前面课程实现的投票应用中,我们分别编写了三个文件: Voting.sol:合约文件 index.html:页面文件 index.js:JS脚本文件
现在对这几个文件分别进行处理,以便应用到Truffle生成的应用中。
Voting.sol
合约文件不需要修改,直接拷贝到 contracts 目录即可: ~/repo/tfapp$ cp ../chapter1/Voting.sol contracts/ ~/repo/tfapp$ ls contracts/ Migrations.sol Voting.sol
index.html
先将页面文件拷贝到 app 目录,覆盖Truffle生成的 index.html : ~/repo/tfapp$ cp ../chapter1/index.html app/
由于Truffle的webpack模版在打包JS脚本时,默认使用 app.js 作为打包入口, 因此,我们将页面文件中对 index.js 的引用改为对 app.js 的引用:
app.js
在Truffle下,我们需要重写与区块链交互的JS脚本。由于使用 webpack 打包,因此 可以使用 ES2015 语法。 你可以在实验环境的编辑器中打开 ~/repo/chapter2/app/javascripts/app.js 查看其内容。
当使用Truffle来编译和部署合约时,框架会将合约的应用接口定义(abi:Application Binary interface) 以及部署地址保存到 build/contracts 目录中同名的json文件中 —— 我们不需要自己记部署地址了! 例如, Voting.sol 的部署信息对应与 build/contracts/Voting.json 文件。利用这个文件就可以创建 投票合约对象: import voting_artifacts from '../../build/contracts/Voting.json' var Voting = contract(voting_artifacts)
合约对象的 deployed() 方法返回一个 Promise ,其解析值为该合约对象的部署实例代理(真正的实例 在链上!),利用这个代理可以执行合约的方法: Voting.deployed() .then(instance => instance.voteForCandidate('Rama')) .then(() => instance.totalVotesFor.call('Rama')) .then(votes => console.log('Rama got votes: ', votes))
思考与练习:
参考教程,升级投票应用代码。
4、迁移脚本
迁移( migration )目录的内容非常重要。Truffle使用该目录下的迁移脚本来管理应用合约的部署。 如果你还记得的话,我们在之前的课程中,是通过在 node 控制台中调用合约对象的 new() 方法来 将投票合约 部署到区块链上。有了 Truffle ,以后再也不需要这么做了。
第一个迁移脚本 1_initial_migration.js 的作用是向区块链部署 Migrations 合约, 这个合约的作用是存储并跟踪已经部署的最新合约。每次运行迁移任务时,Truffle就会向区块链查询获取 已部署好的合约,然后部署新的合约。在部署完成后,这个脚本会更新 Migrations 合约中的 last_completed_migration 字段指向最新部署的合约。
可以简单地把 Migrations 合约当成是一个数据库表,字段 last_completed_migration 总是保持最新状态。 更多细节可见 Truffle官方文档 。
修改迁移脚本
将迁移脚本 2_deploy_contracts.js 的内容修改为以下内容,以便部署我们的投票合约 Voting : var Voting = artifacts.require("./Voting.sol"); module.exports = function(deployer) { deployer.deploy(Voting, ['Rama', 'Nick', 'Jose'], {gas: 290000}); };
从上面的代码可以看出,Truffle框架将向迁移脚本传入一个部署器对象( deployer ),调用其 deploy() 方法即可实现指定合约的部署。
deploy() 方法的第一个参数为要部署合约的编译对象,调用 artifacts.require() 即可直接将合约代码转换为 合约编译对象,例如: artifacts.require('./Voting.sol') 。容易理解,Truffle的 artifacts 对象自动调用 solidity 编译器来编译合约代码文件并返回编译结果对象。
deploy() 方法的最后一个参数是合约实例化选项对象,可以用来指定部署代码所需的油费 —— 别忘了部署合约 也是交易,因此需要烧点油( gas )。gas 数量会随着你的合约大小而变化 —— 确切的说,部署一个合约所需的油费 取决于编译生成的合约字节码,不同的字节码指令对应不同的开销,累加起来就可以估算出部署费用。
对于投票合约而言, 290000个油就足够了 —— 这个价格是我们为部署这个合约愿意承担的最大费用( gasLimit ), 最终的开支可能用不了这么多。当然,如果你的合约很复杂,有可能你愿意买单的这个上限还不够,那么 节点就会返回一个提示,告诉你部署失败,油资不足 —— Out of gas:-)
deploy() 方法的第一个参数和最后一个参数之间,需要按合约构造函数的参数要求依次传入。例如,对于 投票合约,我们只需传入一个候选人名单(数组)。
更新 truffle 配置文件
Truffle在执行任务时,将读取当前目录下的配置文件 truffle.js 。通常我们在该配置文件中 声明要连接的以太坊节点地址,例如 localhost:8545 : require('babel-register') module.exports = { networks: { dev: { host: 'localhost', port: 8545, network_id: '*', gas: 470000 } } }
你应该会注意到gas 选项。这是一个会应用到所有迁移任务的全局变量。当我们调用 deploy() 方法部署一个 合约时,如果没有声明愿意承担的油费,那么Truffle就会采用这个值作为该合约的部署油资。
另一个值得指出的是,Truffle支持将合约部署到多个区块链网络,例如开发网络、私有网络、测试网或公网。 在上面的配置文件中,我们仅定义了一个用于开发的网络 dev —— 你知道它指向的是ganache模拟器,Truffle 在执行命令时将自动连接到这个网络。
思考与练习:
参考教程,编写投票合约的迁移脚本。
5、合约的编译与部署
在Truffle中执行 compile 命令来编译 contracts 下的所有合约: ~/repo/tfapp$ truffle compile Compiling Migrations.sol... Compiling Voting.sol... Writing artifacts to ./build/contracts
在Truffle中执行 migrate 命令将编译后的合约部署到链上: ~/repo/tfapp$ truffle migrate Running migration: 1_initial_migration.js Deploying Migrations... Migrations: 0x3cee101c94f8a06d549334372181bc5a7b3a8bee Saving successful migration to network... Saving artifacts... Running migration: 2_deploy_contracts.js Deploying Voting... Voting: 0xd24a32f0ee12f5e9d233a2ebab5a53d4d4986203 Saving successful migration to network... Saving artifacts...
如果由于油费不足而导致部署失败,可以尝试增加 migrations/2_deploy_contracts.js 里面的 gas 值。比如: deployer.deploy(Voting, ['Rama', 'Nick', 'Jose'], {gas: 500000})
如果希望自选一个账户来部署合约,而不是使用默认的 accounts[0] ,可以在迁移脚本中使用 from 选项指定,例如: deployer.deploy(Voting, ['Rama', 'Nick', 'Jose'], {gas: 500000,from:'0x8cff691c888afe73ffa3965db39be96ba3b34e49'}) 也可以在 truffle.js 中指定默认的用来与区块链交互的账户地址: module.exports = { networks: { dev: { host: 'localhost', port: 8545, network_id: '*', gas: 470000, from: '0x8cff691c888afe73ffa3965db39be96ba3b34e49' } } }
思考与练习:
参考教程,编译并部署投票合约。
6、控制台和网页交互
部署顺利的话,现在就可以通过控制台和网页与合约进行交互了。
使用Truffle控制台
在第二个终端中输入 truffle console 命令进入控制台: ~/repo/tfapp$ truffle console truffle(development)> Voting.deployed().then(function(contractInstance) {contractInstance.voteForCandidate('Rama').then(function(v) {console.log(v)})}) { blockHash: '0x7229f668db0ac335cdd0c4c86e0394a35dd471a1095b8fafb52ebd7671433156', blockNumber: 469628, contractAddress: null, .... .... truffle(default)> Voting.deployed().then(function(contractInstance) {contractInstance.totalVotesFor.call('Rama').then(function(v) {console.log(v)})}) { [String: '1'] s: 1, e: 0, c: [ 1] }
注意,truffle 的所有调用都会返回 promise ,这就是为什么每个响应都被包裹在 then() 函数里的原因。
通过网页交互
进入 build 目录,先建立网页资源文件的符号连接,然后启动web服务器: ~/repo/tfapp/build$ ln -s ~/repo/common/lib lib ~/repo/tfapp/build$ ln -s ~/repo/common/fonts fonts ~/repo/tfapp/build$ python -m SimpleHTTPServer
现在,在实验环境的嵌入浏览器中点击刷新按钮。BING!
思考与练习:
参考教程,分别使用控制台和网页,实现与合约的交互。
7、总结
你已经成功地利用Truffle构建了去中心化的投票应用。恭喜!
下面是进一步学习以太坊的相关资源链接: 以太坊白皮书: https://github.com/ethereum/wiki/wiki/White-Paper Solidity语言手册: http://solidity.readthedocs.io/en/develop/ stackexchange上的以太坊版块: http://ethereum.stackexchange.com/ 超级有帮助的gitter社区: web3.js , solidity 了解实时gas价格: http://ethgasstation.info/ 跟踪以太坊的最新动态: https://www.reddit.com/r/ethereum/
在下面的课程中,我们将学习通证的使用,并让投票应用支持通证的购买和消费。
4、使用数字代币/通证
1、概述
在以太坊中,你会遇到的一个重要概念就是通证( token ),也就是常说的加密数字币,或者代币:
通证就是在以太坊上构建的数字资产,可以用它来表示现实世界里的东西,比如黄金,或者是自己 的数字资产(就像货币一样)。通证实际上就是智能合约,并没有什么神奇之处。 黄金通证:银行可以有 1 千克的黄金储备,然后发行 1千个通证。买 100 个 黄金通证 就等于买 100 克的黄金。 公司股票:公司股票可以用以太坊上的代币来表示。通过支付以太,人们可以购买公司股票。 游戏币:在一个多玩家游戏中,游戏者可以用以太购买游戏币,并在游戏中进行消费。 Golem通证:这是一个基于以太坊的真实项目,个人可以通过租售空闲的 CPU 来赚取通证。 忠诚度积分:商店可以给购物者发行通证作为忠诚度积分,它可以在将来作为现金回收,或是在第三方市场售卖。
在合约中如何实现通证,实际上并没有限制。但是,以太坊有一个叫做 ERC20 的通证标准,该标准还 在不断进化中。 ERC20 通证的优点是很容易与其他的符合 ERC20 标准的通证进行交换,同时,也更容易 将你的通证集成到其他DApp中。
在下一节中,我们为投票应用增加通证和支付功能。总的来说,后续课程将涵盖以下内容: 学习并掌握新的数据类型,比如结构体( struct ),以便在区块链上组织和存储数据 理解通证概念并实现投票应用的通证 学习使用以太币进行支付,以太币是以太坊区块链平台的数字加密货币。
2、加权投票应用
一提到投票,你通常会想起普通的选举,例如,通过投票来选出一个国家的首相或总统。在这种情况下, 每个公民都会有一票,可以投给他们支持的候选人。
还有另外一种加权投票( weighted voting ),它常常用于公开上市交易的公司。 在这些公司,股东的投票权取决于其持有的股票数量。比如,如果你拥有 10,000 股公司股票,你就有 10,000 个投票权(而不是普通选举中的一票)。关于加权投票的更多内容可以查看 这里 。
例如,假设有一个叫做 Block 的上市公司。公司有 3 个空闲职位,分别是总裁、副总裁和部长,以及 一组候选人。该公司希望通过股东投票的方式来决定哪个候选人得到哪个职位。获得最高票数的候选人 将会成为总裁,然后是副总裁,最后是部长。
针对这个应用场景,我们可以构建一个DApp来发行公司股票,该应用允许任何人购买股票从而成为股东。 股东基于其拥有的股票数为候选人投票。例如,如果你持有10,000 股,你可以一个候选人投 5,000 股, 另一个候选人 3,000 股,第三个候选人 2,000 股。
这里是我们将要在本课程实现应用的图示,任何人都可以调用合约的 buy() 方法来购买公司发行的 股票通证,然后就可以调用合约的 voteForCandidate() 方法为特定的候选人投票:
在下一节,我们将会勾勒出实现框架,并随后实现构建完整应用的所有组件。
3、实现思路
经过简单地思考,我们将按以下思路来实现加权投票应用:
首先初始化一个新的 truffle 项目,然后修改关键代码文件: 投票合约: Voting.sol 合约迁移脚本: 2_deploy_contracts.js 前端代码: index.html 、 app.js 和 app.css
在部署合约时初始化参与竞争的候选人名单。从之前的课程中,相信你已经知道了如何实现这一点。 我们将会在迁移脚本 2_deploy_contracs.js 中完成这个任务。
由于投票人需要先持有公司股票。所以,我们还需要在部署合约时初始化公司发行的股票总量。 这些股票就是构成公司的数字资产。在以太坊的世界中,这些数字资产被称为通证( Token )。 因此,从现在开始,我们将会把这些股票称为股票通证。
需要指出的是,股票可以看做是一种通证,但是并非所有的以太坊通证都是股票。股票仅仅是 我们前一节中提到的通证使用场景的一种。
我们还需要向投票合约中增加一个新的方法,以便任何人都可以购买这些通证。容易理解,投票人 给候选人投票时将使用(消耗)这些股票通证。
接下来还需要添加一个方法来查询投票人的信息,以及他们分别给谁投了票、总共持有多少股票通证、 还有多少可用的通证余额等等。
为了跟踪所有这些数据,我们需要使用几个 mapping 类型的字段,同时还需要引入新的数据类型 struct (结构体) 来组织投票人信息。
项目初始化
和原来一样,我们使用 truffle 的 webpack 项目模版来初始化一个新项目, 并从 contracts 目录下移除无用的合约文件: ~$ mkdir -p ~/repo/tkapp ~$ cd ~/repo/tkapp ~/repo/tkapp$ truffle unbox webpack ~/repo/tkapp$ rm contracts/ConvertLib.sol contracts/MetaCoin.sol
思考与练习:
参考教程,在 repo 目录下建立新项目 tkapp 并进行初始化。
4、加权投票合约设计
新的投票合约要比之前复杂一些:
之前的投票合约仅仅包含两个状态:数组 candidateList 保存候选人名单,字典 votesReceived 跟踪每个候选人获得的投票。
在加权投票合约中,我们需要额外跟踪一些数据: 投票人信息: solidity 的结构体( struct )类型可以将相关数据组织在一起。用结构体来存储投票人 信息非常好。如果你之前不了解struct类型,可以将其视为面向对象开发中没有方法的类。我们将使用 一个struct来存储投票人的账户、已经购买的股票通证数量以及给每个候选人投票时所用的股票数量。例如: struct voter { address voterAddress; //投票人账户地址 uint tokensBought; //投票人持有的股票通证总量 uint[] tokensUsedPerCandidate; //为每个候选人消耗的股票通证数量 } 投票人信息字典:使用一个 mapping 字典来保存所有的投票人信息,键为投票人账户地址,值为投票人信息。 这样给定一个投票人的账户地址,就可以很方面地提取他的相关信息。我们使用 voterInfo 来表示该字典。 例如: mapping (address => voter) public voterInfo 。 股票通证的相关信息:使用 totalTokens 来保存通证发行总量, balanceTokens 保存通证余额, tokenPrice 保存 通证的价格。
在部署合约时,除了指定候选人名单,我们还需要声明股票通证发行总量和股票单价。 因此在合约的构造函数中,需要补充声明这些参数。例如: contract Voting{ function Voting(uint tokens, uint pricePerToken, bytes32[] candidateNames) public {} }
当股东调用 voteForCandidate() 方法投票给特定候选人时,还需要声明其支持力度 —— 用多少股票来支持 这个候选人。因此,我们需要为该方法添加额外的参数以便传入股票通证数量。例如: contract Voting{ function voteForCandidate(bytes32 candidate, uint votesInTokens) public {} }
任何人都可以调用 buy() 方法来购买公司发行的股票通证,从而成为公司的股东并获得投票权。 你应该已经注意到了该方法的 payable 修饰符。在Sodility合约中,只有声明为 payable 的方法, 才可以接收支付的货币( msg.value 值)。例如: contract Voting{ function buy() payable public returns (uint) { //使用msg.value来读取用户的支付金额,这要求方法必须具有payable声明 } }
可以在实验环境编辑器中打开 ~/repo/chapter3/contracts/Voting.sol 来查看完成后的加权投票合约。
思考与练习:
阅读教程,理解加权投票合约与简单投票合约的区别。
5、合约实现 —— 购买通证
合约的 buy() 方法用于提供购买股票的接口。注意关键字 payable ,有了它买股票的人才可以付钱给你。 接收钱没有比这个再简单的了! function buy() payable public returns (uint) { uint tokensToBuy = msg.value / tokenPrice; //根据购买金额和通证单价,计算出购买量 require(tokensToBuy <= balanceTokens); //继续执行合约需要确认合约的通证余额不小于购买量 voterInfo[msg.sender].voterAddress = msg.sender; //保存购买人地址 voterInfo[msg.sender].tokensBought += tokensToBuy; //更新购买人持股数量 balanceTokens -= tokensToBuy; //将售出的通证数量从合约的余额中剔除 return tokensToBuy; //返回本次购买的通证数量 }
当用户(或程序)调用合约的 buy() 方法时,需要在请求消息里利用 value 属性设置 用于购买股票通证的以太币金额。例如: contract.buy({ value:web3.toWei('1','ether'), //购买者支付的以太币金额 from:web3.eth.accounts[1] //购买者账户地址 })
在合约的 payable 方法实现代码中使用 msg.value 来读取用户支付的以太币数额。 基于用户支付额和股票通证单价,就可以计算出购买数量,并将这些通证赋予购买人, 购买人的账户地址可以通过 msg.sender 获取。
当然,也可以从 truffle 控制台调用 buy() 方法来购买股票通证: truffle(development)> Voting.deployed().then(function(contract) {contract.buy({value: web3.toWei('1', 'ether'), from: web3.eth.accounts[1]})})
思考与练习:
参考教程,实现加权投票合约的 buy() 方法。
6、合约实现 —— 加权投票
如前所述,加权投票方法不仅要指定候选人名称,还要指定使用多少股票通证来支持该候选人。 我们分别用 candidate 和 votesInTokens 来表示这两个参数: function voteForCandidate(bytes32 candidate, uint votesInTokens) public {}
在投票人调用 voteForCandidate() 方法投票时,我们不仅需要为指定的候选人增加其投票数,还需要 跟踪投票人的相关信息,比如投票人是谁(即其账户地址),以及给每个候选人投了多少票。因此在 该方法的开始部分,检查如果是该投票人第一次参与投票的话,首先初始化该投票人的 voterInfo 结构: if (voterInfo[msg.sender].tokensUsedPerCandidate.length == 0) { for(uint i = 0; i < candidateList.length; i++) { voterInfo[msg.sender].tokensUsedPerCandidate.push(0); //该投票人为每个候选人投入的通证数量初始化为0 } }
接下来我们计算该投票人当前的有效持股数量 —— 从该投票人的持股数量中扣除其为所有投票人已经 消耗的股票通证数量: uint availableTokens = voterInfo[msg.sender].tokensBought - totalTokensUsed(voterInfo[msg.sender].tokensUsedPerCandidate)
显然,在合约继续执行之前,需要满足条件 —— 投票人的有效持股数量不小于本次投票使用的股票通证数量: require (availableTokens >= votesInTokens)
如果投票人依然持有足够数量的股票通证,我们就更新候选人获得的票数,同时更新投票人的通证使用记录: votesReceived[candidate] += votesInTokens; voterInfo[msg.sender].tokensUsedPerCandidate[index] += votesInTokens;
参考教程,实现加权投票合约的 voteForCandidate() 方法。
7、合约实现 —— 转账
当一个用户调用 buy() 方法发送以太来购买了合约发行的股票通证后,合约收到的资金去了哪里?
所有收到的资金(以太)都在这个投票合约里。每个合约都有它自己的地址,因此也是一个账户。 在以太坊里,这种账户被称为合约账户( Contract Account ),而之前的人员账户,则被称为外控账户 ( External Controlled Account )。
因此,合约的地址里存着这些销售收益。
我们新增加的 transferTo() 方法,可以将合约里的资金转移到指定账户: function transferTo(address account) public { account.transfer(this.balance); }
注意! transferTo() 方法的当前实现,并没有限制调用者,因此任何人都可以调用该方法从而 转走投票合约账户里的资金!在生产系统中,你必须添加一些限制条件来避免上面的资金漏洞,例如, 检查目标账户是否在一个白名单里。
其他
合约里面剩下的方法都是辅助性的 getter 方法,仅仅用来返回合约变量的值。
注意 tokensSold() 等方法声明中的 constant 修饰符,这表明该方法是 只读 的,即方法的执行 并不会改变区块链的状态,因此执行这些交易不会耗费任何 gas 。
参考教程,实现加权投票合约的 transferTo() 方法。
8、合约部署
与之前的课程类似,我们修改迁移脚本 2_deploy_contracts.js 来自动化投票合约的部署。 不过由于新的加权投票合约的构造函数声明了额外的参数,因此需要在迁移脚本中传入两个额外的参数 : var Voting = artifacts.require("./Voting.sol"); module.exports = function(deployer) { deployer.deploy(Voting, 10000, web3.toWei('0.01', 'ether'), ['Rama', 'Nick', 'Jose']); };
在上面的代码中,我们部署的合约发行了10000个股票通证,单价为 0.01 以太。由于所有的价格需要以 Wei 为单位 计价,所以我们需要用 toWei() 方法将Ether转换为 Wei。
以太币面值
Wei 是 Ether 的最小面值。1 Ether 等于 1000000000000000000 Wei —— 18个0,我替你查了:-)。 你可以把它当成是美分与美元,就像 Nickel(5 美分),Dime(10 美分),Quarter(25 美分),Ether 也有不同面值。其他面值如下: kwei/babbage mwei/lovelace gwei/shannon szabo finney ether kether/grand/einstein mether gether tether
你可以在 truffle 控制台,执行 web3.toWei(1, 'ether') 来看一下ether(或其他面值)与 wei 之间 的转换关系。例如: truffle(development)> web3.toWei(1,'ether')
编译与部署
现在可以编译合约并将其部署到区块链了: ~/repo/tkapp$ truffle compile Compiling Migrations.sol... Compiling Voting.sol... Writing artifacts to ./build/contracts ~/repo/tkapp$ truffle migrate Running migration: 1_initial_migration.js Deploying Migrations... Migrations: 0x3cee101c94f8a06d549334372181bc5a7b3a8bee Saving successful migration to network... Saving artifacts... Running migration: 2_deploy_contracts.js Deploying Voting... Voting: 0xd24a32f0ee12f5e9d233a2ebab5a53d4d4986203 Saving successful migration to network... Saving artifacts...
参考教程,编译并部署加权投票合约。
9、控制台交互
成功地将合约部署到了 ganache 后,执行 truffle console 进入控制台,让我们 和合约互动一下: 一个候选人(比如 Nick)有多少投票? truffle(development)> Voting.deployed().then(function(instance) {instance.totalVotesFor.call('Nick').then(function(i) {console.log(i)})}) 一共初始化发行了多少通证? truffle(development)> Voting.deployed().then(function(instance) {console.log(instance.totalTokens().then(function(v) {console.log(v)}))}) 已经售出了多少通证? truffle(development)> Voting.deployed().then(function(instance) {console.log(instance.tokensSold().then(function(v) {console.log(v)}))}) 购买 100个通证 truffle(development)> Voting.deployed().then(function(instance) {console.log(instance.buy({value: web3.toWei('1', 'ether')}).then(function(v) {console.log(v)}))}) 购买以后账户余额是多少? truffle(development)> web3.eth.getBalance(web3.eth.accounts[0]) 已经售出了多少通证? Voting.deployed().then(function(instance) {console.log(instance.tokensSold().then(function(v) {console.log(v)}))}) 给 Jose 投 25 个 通证,给 Rama 和 Nick 各投 10 个 通证。 truffle(development)> Voting.deployed().then(function(instance) {console.log(instance.voteForCandidate('Jose', 25).then(function(v) {console.log(v)}))}) truffle(development)> Voting.deployed().then(function(instance) {console.log(instance.voteForCandidate('Rama', 10).then(function(v) {console.log(v)}))}) truffle(development)> Voting.deployed().then(function(instance) {console.log(instance.voteForCandidate('Nick', 10).then(function(v) {console.log(v)}))}) 查询你所投账户的投票人信息(除非用了其他账户,否则你的账户默认是 web3.eth.accounts[0]) truffle(development)> Voting.deployed().then(function(instance) {console.log(instance.voterDetails('0x004ee719ff5b8220b14acb2eac69ab9a8221044b').then(function(v) {console.log(v)}))}) 现在候选人Rama有多少投票? truffle(development)> Voting.deployed().then(function(instance) {instance.totalVotesFor.call('Rama').then(function(i) {console.log(i)})})
在控制台里查一下,现在合约里有多少以太币?
10、网页交互
现在,你已经知道了新的投票合约可以如约工作。让我们开始构建前端逻辑,以便用户能够通过网页浏览器与合约交互:
HTML
在实验环境编辑器中打开 ~/repo/chapter3/app/index.html 来查看已经完成的网页。
如果仔细审查代码的话,你会发现网页中已经没有硬编码的值了。候选人的名字将通过向部署好 的合约查询来进行填充。
网页也会显示公司发行的股票通证总量,以及已售出和剩余的通证量。
Javascript
在实验环境编辑器中打开 ~/repo/chapter3/app/javascripts/app.js 来查看已经完成的JS脚本。
通过移除候选者姓名等等的硬编码,我们已经大幅改进了 HTML 文件。我们会使用 javascript/web3js 来填充 HTML页面里的所有值,并实现查询投票人信息的额外功能。
如果你对 JavaScript 不太熟悉,这些代码可能略显复杂。那么最好先理解 populateCandidates() 函数的实现。
我们推荐用 JavaScript 自己实现,预置代码仅作参考之用。可以按照下述指引帮助实现: 创建一个 Voting 合约的实例 在页面加载时,初始化并创建 web3 对象。(第一步和第二步与之前的课程一模一样) 创建一个在页面加载时调用的函数,它需要: 使用 Voting 合约对象,向区块链查询来获取所有的候选者姓名并填充表格。 再次查询区块链得到每个候选人所获得的所有投票并填充表格的列。 填充 token 信息,比如所有初始化的 token,剩余 token,已售出的 token 以及 token 成本。 实现 buyTokens 函数,它在上一节的 html 里面调用。你已经在控制台交互一节中购买了 token。buyTokens 代码与那一节一样不可或缺。 类似地,实现 lookupVoterInfo 函数来打印一个投票人的细节。
CSS
在实验环境编辑器中打开 ~/repo/chapter3/app/stylesheetss/app.css 来查看已经完成的样式表。
启动web服务
和之前一样,执行以下命令进行构建: ~/repo/tkapp$ webpack
然后进入 build 目录,启动轻量web服务器: ~/repo/tkapp/build$ python -m SimpleHTTPServer
现在,在试验环境的嵌入浏览器中点击刷新按钮。如果一切顺利,你可以看到网页, 可以输入一个账户地址(投票人的地址),观察他们的投票行为和股票通证数量的变化。 并且可以购买更多的股票通证,为任意候选者投票并查看投票人信息。
思考与练习:
现在合约的实现方式,用户购买股票通证并用通证投票。但是他们投票的方式是向合约发送通证。如果他们必须在未来的 选举中投票怎么办?他们所有的通证都转移到了合约中!
进一步改进合约的方式是,添加加入一个方法以便于用户能够在投票结束后拿回他们的通证。请在合约中实现这一方法: 查询用户投票的所有通证,并将这些通证返还给用户。
区块链
2018-05-02 22:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
这是以太坊创始人Vitalik接受采访的视频及中文字幕全文。在视频中,Vitalik介绍了从BitTorrent开始的去中心化网络,从比特币开始的区块链及去中心化货币,并回答了以太坊是什么、以太坊的设计思路和愿景等问题。
视频链接: http://blog.hubwiz.com/2018/04/07/ethereum-vitalik-talk/ 如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 去中心化电商DApp实战开发
《以太坊是什么》中文字幕全文
感谢现代通信技术的发展,让我们有能力创造去中心化的技术,剔除中间人并 允许用户之间直接通过全球网络交互。
去中心化在过去的十年里越来越重要,它非常有助于大量减少成本和障碍、 消除单点故障 、防止审查并确保透明度和参与互动各方之间的信任。
BitTorrent是一个 文件共享网络,它是在21世纪初开发的第一个去中心化的应用程序。 BitTorrent允许任何人分享任何类型的文件给世界上任何其他人。它让人们可以迅速、简单地分发内容 即使他们没有资源来建立自己的网站或服务器。
五年后,中本聪提出了区块链的想法,一种 分布式数据库,并用它来构建比特币,世界上第一个 去中心化的货币 。去中心化货币,例如比特币,允许人们随时随地汇款到世界各地而不必考虑国家边界,并且手续费很少。
比特币越来越多地被用于国际汇款、小额付款和电子商务。去中心化的 金融应用、云计算 信息沟通和分布式治理也将很快到来。以太坊是一个专门设计的平台,帮助人们建立这些 去中心化应用。
以太坊客户端 ,我们称之为以太坊浏览器,可以利用点对点网络 发送消息。以太坊是一个广义的 区块链,内置编程 语言允许人们使用区块链来创建任何类型的去中心化应用程序。
以太坊可以用来建立完全可信而且透明的金融应用,因为它们运行在区块链上;线上密码学 安全系统用于管理你的资产与合约;去中心化社交网络和消息系统允许用户拥有他们自己的数据; 还可以建立去中心化云计算系统,使用个人未充分利用的计算资源,比如CPU时间和硬盘 空间等 ; 还有网上投票工具、分布式治理等。
以太坊最令人兴奋的应用,可能是我们还没有想到的那些。像所有用于创新的平台一样, 例如互联网,本身采用的底层协议并不总是很容易预测可以用来做什么。而Gmail、Facebook、Twitter、Instagram这些现代互联网应用,整体上都是早期万维网发展的结果,以及JavaScript,这个用于web的编程语言,源于20世纪90年代。
同样,通过提供一个通用可编程的区块链,并将它打包进一个客户端,从而让任何人都可以使用。以太坊项目希望为金融业、点对点商业,分布式治理和人类整体的合作做同样的事情。
现在的问题是,你会 在以太坊之上构建什么样的应用?
区块链
2018-05-02 21:44:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
以太坊(Ethereum)以基金会为核心,形成了一个庞大的社区和生态。以太坊官网主要分为四类:以太坊基金会官网、以太坊代码与文档官网、以太坊运行监控与统计官网和以太坊官方社区账号/频道。
以太坊基金会官网
以太坊基金会是整个以太坊项目的发动机,其域名为ethereum.org。 以太坊基金会官网 以太坊官方FAQ集 以太坊官方博客
以太坊代码与文档官网
以太坊的代码都是开源的,托管在github.com上,文档则分别在ethdocs.org和readthedocs.org上。 以太坊官方Github仓库 以太坊官方文档 Solidity官方文档 如果你希望马上开始学习以太坊应用开发,可以访问汇智网提供的出色的在线互动教程: 以太坊智能合约与应用开发入门 去中心化电商应用实战开发:以太坊+IPFS+NodeJS+Express+MongoDB
以太坊网络运行监控与统计分析
以太坊的全网运行监控与统计分析,可以从ethstats.net获得: 以太坊运行监控与统计
以太坊官方社区账号/频道
以太坊基金会在众多的社交平台都开设有官方账号与频道: 以太坊官方Youtube频道 以太坊官方Reddit频道 以太坊官方Gitter频道 以太坊官方Twitter账号 以太坊官方Stackexchange问答 以太坊官方Facebook账号 以太坊官方Meetups账号
区块链
2018-05-02 20:28:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 在学习了最基础的一些以太坊知识以及开发框架搭建完成之后,可以尝试开发自己的第一个DApp,此处使用Truffle开发框架,Remix编译环境,Genache测试客户端,具体操作如下。
此处参照MetaCoin创建自己的模拟转账Dapp 名称为RuoliCoin
1、创建工作目录
创建RuoliCoin目录,在此目录下打开命令行 执行 Truffle 初始化命令,如下: truffle unbox webpack
即可创建基础Truffle框架。
2、创建Solidity智能合约文件
在RuoliCoin/contracts 目录下删除原有的 ConvertLib.sol、MetaCoin.sol两个文件,新建RuoliCoin.sol文件,使用Web版本的Remix打开此文件,进行智能合约的编写操作,最后编写好的智能合约内容如下: pragma solidity ^0.4.23; contract RuoliCoin { mapping (address => uint) balances; event Transfer(address indexed _from, address indexed _to, uint256 _value); constructor() public { balances[msg.sender] = 10000; } function sendCoin(address receiver, uint amount) public returns(bool sufficient) { if (balances[msg.sender] < amount) return false; balances[msg.sender] -= amount; balances[receiver] += amount; emit Transfer(msg.sender, receiver, amount); return true; } function getBalance(address addr) public view returns(uint) { return balances[addr]; } }
3、部署至测试环境
修改truffle.js 文件,指定本地 RCP Server 地址(启动Ganache后主界面有显示),内容如下: module.exports = { networks: { development: { host: 'localhost', port: '7545', network_id: '*' // Match any network id } } };
migrations 目录下 1_initial_migration.js 、2_deploy_contracts.js 两个文件 非常重要切勿删除,在此目录下将 2_deploy_contracts.js 进行修改 内容如下: var RuoliCoin = artifacts.require("./RuoliCoin.sol"); module.exports = function(deployer) { deployer.deploy(RuoliCoin); };

在RuoliCoin目录下使用命令行编译并部署智能合约文件,如下: PS C:\Workspace\Ruoli-Code\Truffle\RuoliCoin> truffle compile PS C:\Workspace\Ruoli-Code\Truffle\RuoliCoin> truffle deploy Using network 'development'. Running migration: 1_initial_migration.js Deploying Migrations... ... 0x73e55a0f780c6780039abc3feb8b5e1243744135096e441668e8ab55579e51db Migrations: 0xb81237dd01159a36a5ac3c760d227bbafe3341ea Saving successful migration to network... ... 0xc5be542ec02f5513ec21e441c54bd31f0c86221da26ed518a2da25c190faa24b Saving artifacts... Running migration: 2_deploy_contracts.js Deploying RuoliCoin... ... 0xd4c85531d5d83c61f79485c43e6e4146d51b909c8b73bf5d88b60aa990cf1d08 RuoliCoin: 0x6ba286f3115994baf1fed1159e81f77c9e1cd4fa Saving successful migration to network... ... 0xc8d5533c11181c87e6b60d4863cdebb450a2404134aea03a573ce6886905a00b Saving artifacts... PS C:\Workspace\Ruoli-Code\Truffle\RuoliCoin>
此时查看 Ganache界面中 第一个账户的 ETH 余额已减少,说明部署成功。
4、编写页面文件
在 app 目录下删除所有文件,新建index.html、javascripts/app.js 两个文件,文件内容如下:
index.html 如下: RuoliCoin - Truffle Demo


javascripts/app.js内容如下: //导入CSS //import "../stylesheets/app.css"; //导入web3和truffle-contract库 import { default as Web3} from 'web3'; import { default as contract } from 'truffle-contract' //导入Hello合约的ABI文件 import RuoliCoin_artifacts from '../../build/contracts/RuoliCoin.json' //获取Hello合约对象 var RuoliCoinContract = contract(RuoliCoin_artifacts); var ruoliCoinInstance = null; var accounts; var account; window.App = { init: function() { //设置web3连接 RuoliCoinContract.setProvider(web3.currentProvider); // Get the initial account balance so it can be displayed. web3.eth.getAccounts(function(err, accs) { if (err != null) { alert("There was an error fetching your accounts."); return; } if (accs.length == 0) { alert("Couldn't get any accounts! Make sure your Ethereum client is configured correctly."); return; } accounts = accs; account = accounts[0]; }); //instance为Hello合约部署实例 RuoliCoinContract.deployed().then(function(instance){ ruoliCoinInstance=instance; var event=ruoliCoinInstance.Transfer(); event.watch(function(error,result){ alert(error); console.log(result); }); }).catch(function(e){ console.log(e, null); }); }, //封装合约中的say()方法调用过程,供javascript调用 transfer: function(transferAddr,amount, callback){ //调用Hello合约中的say()方法,并传入参数name ruoliCoinInstance.sendCoin(transferAddr,amount,{from: account}).then(function(result){ //将返回结果传入回调函数 callback(null, result); }); }, getBalance:function(balanceAddr,callback){ //调用Hello合约中的say()方法,并传入参数name ruoliCoinInstance.getBalance.call(balanceAddr,{from: account}).then(function(result){ //将返回结果传入回调函数 callback(null, result); }); } }; window.addEventListener('load', function() { //设置web3连接 http://127.0.0.1:7545 为Ganache提供的节点链接 window.web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:7545")); App.init(); });
运行界面如下:

5、运行与测试
在RuoliCoin 根目录 执行启动命令 npm run dev ,如下: PS C:\Workspace\Ruoli-Code\Truffle\Metacoin> npm run dev > truffle-init-webpack@0.0.2 dev C:\Workspace\Ruoli-Code\Truffle\Metacoin > webpack-dev-server Project is running at http://localhost:8081/ webpack output is served from / Hash: 8b5b7df27e0385bf011d Version: webpack 2.7.0 Time: 3239ms Asset Size Chunks Chunk Names app.js 1.68 MB 0 [emitted] [big] main index.html 925 bytes [emitted] chunk {0} app.js (main) 1.66 MB [entry] [rendered] [71] ./app/javascripts/app.js 3.64 kB {0} [built] [72] (webpack)-dev-server/client?http://localhost:8081 7.93 kB {0} [built] [73] ./build/contracts/MetaCoin.json 47.8 kB {0} [built] [111] ./~/loglevel/lib/loglevel.js 7.86 kB {0} [built] [117] ./~/querystring-es3/index.js 127 bytes {0} [built] [119] ./~/strip-ansi/index.js 161 bytes {0} [built] [122] ./app/stylesheets/app.css 905 bytes {0} [built] [163] ./~/truffle-contract/index.js 2.64 kB {0} [built] [197] ./~/url/url.js 23.3 kB {0} [built] [199] ./~/web3/index.js 193 bytes {0} [built] [233] (webpack)-dev-server/client/overlay.js 3.67 kB {0} [built] [234] (webpack)-dev-server/client/socket.js 1.08 kB {0} [built] [235] (webpack)/hot nonrecursive ^\.\/log$ 160 bytes {0} [built] [236] (webpack)/hot/emitter.js 77 bytes {0} [built] [237] multi (webpack)-dev-server/client?http://localhost:8081 ./app/javascripts/app.js 40 bytes {0} [built] + 223 hidden modules webpack: Compiled successfully.
访问 上面提示的链接地址:http://localhost:8081/
即可访问到页面,输入转入地址和金额即可进行代币转账操作,输入查询余额地址即可进行查询余额操作。
区块链
2018-05-02 20:18:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
truffle.js是以太坊开发框架truffle的配置文件,本手册介绍truffle.js 配置文件的位置、windows下的命名冲突解决方案以及各种配置选项。 如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 去中心化电商DApp实战开发
truffle配置文件位置
truffle项目的配置文件位于项目的根目录下,名称为truffle.js。这个文件是一个Javascript脚本,可以在其中执行任意 必要的代码来创建适合你的配置。truffle.js必须导出一个表征你的项目配置的对象,例如: module.exports = { networks: { development: { host: "127.0.0.1", port: 8545, network_id: "*" // 可匹配任意网络 } } };
在这个创建truffle项目时生成的默认配置文件中,声明了一个名为 development 的以太坊节点,该节点在地址 127.0.0.1:8545 监听。
WINDOWS下的命名冲突解析
在Windows命令行使用truffle时,默认的配置文件名与truffle冲突 —— 当你在命令行输入truffle时,windows实际上会调用项目目录下 的配置脚本truffle.js。
可以有几种方法解决这一冲突: 输入truffle.cmd全称,例如: D:\ez-dapp> truffle.cmd compile 修改PATHEXT环境变量,将 .js 后缀从可执行后缀中删除。 将truffle.js更名为 truffle-config.js 。 使用不会产生冲突的Windows的power Shell或Git BASH。
网络节点选项
指定在部署合约、发送交易时使用哪个网络节点。当在某个特定的网络节点上编译或部署合约时,合约会缓存起来方便后续使用。 当truffle的合约抽象层检查到你连到某个网络节点时,它会使用这个这个网络节点上原有的缓存合约来简化部署流程。
下述的networks对象,通过一个网络名做为配置的键,值对应定义了其网络参数。networks的对应选项不是必须的,但如果一旦指定, 每个网络必须定义一个对应的network_id。如果希望声明一个默认网络,可以通过将netword_id的值标记为default来实现, 当没有匹配到其它的网络时,就会使用默认网络。需要注意的是整个配置中,应该有且仅有一个default的网络。一般来说, 默认网络主要用于开发,配置,合约等数据没有长期保存的需要,网络ID也会因TestRPC的重启而频繁改变时。
网络节点名称在通过用户接口调用时使用。例如,在部署合约时使用 --network 选项指定要使用的网络节点: $ truffle migrate --network live
live 是truffle.js中定义的某个网络节点: networks: { development: { host: "127.0.0.1", port: 8545, network_id: "*" // match any network }, live: { host: "178.25.19.88", // Random IP for example purposes (do not use) port: 80, network_id: 1, // Ethereum public network // optional config values: // gas // gasPrice // from - default address to use for any transaction Truffle makes during migrations // provider - web3 provider instance Truffle should use to talk to the Ethereum network. // - function that returns a web3 provider instance (see below.) // - if specified, host and port are ignored. } }
对于每一个配置的网络节点,在未明确设置以下交易参数时,使用其默认值: gas:部署合约的油耗上限,默认值:4712388 gasPrice:部署合约时的油价,默认值:100000000000 wei,即100 shannon from:执行迁移脚本时使用的账户,默认使用节点旳第一个账户 provider:默认的provider使用host和port选项构造: new Web3.providers.HttpProvider("http://host:port")
对于配置的每个网络节点,你可以设置provider或host/port,但不能同时使用。如果你需要一个 HTTP provider,推荐使用host/port选项,如果需要一个定制的provider,例如HDWalletProvider, 那么必须使用provider选项。
合约编译输出目录选项:CONTRACTS_BUILD_DIRECTORY
合约编译的默认输出目录是项目根目录下的./build/contracts,不过这一点可以在配置文件中 使用contracts_build_directory配置项进行修改。
例如,你可以将合约编译结果存放在项目根目录的./output/contracts目录下: module.exports = { contracts_build_directory: "./output", networks: { development: { host: "127.0.0.1", port: 8545, network_id: "*", } } };
编译生成的合约构件也可以不放在项目目录下,例如: module.exports = { contracts_build_directory: "../../../output", networks: { development: { host: "127.0.0.1", port: 8545, network_id: "*", } } };
当然你也可以使用绝对路径,不过我们不推荐这么做,因为如果在其他机器上 构建你的项目,可能会找不到你设定的绝对路径。如果在windows下使用绝对 路径,记得转义反斜杠,例如: C:\
区块链
2018-05-02 18:53:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
一个适合区块链新手的以太坊DApp开发教程:
http://xc.hubwiz.com/course/5a952991adb3847553d205d1
一个用区块链、星际文件系统(IPFS)、Node.js和MongoDB来构建电商平台:
http://xc.hubwiz.com/course/5abbb7acc02e6b6a59171dd6
收集整理了一些免费区块链、以太坊技术开发相关的文件,有需要的可以下载,文件链接: web3.js API官方文档中文版: https://pan.baidu.com/s/1hOV9hEzi7hFxJCL4LTvC6g 以太坊官方文档中文版 : https://pan.baidu.com/s/1ktODJKLMBmkOsi8MPrpIJA 以太坊白皮书中文版 : https://pan.baidu.com/s/1bzAFnzJ35hlQxJ2J4Oj-Ow Solidity的官方文档中文版 : https://pan.baidu.com/s/18yp9XjEqAHpiFm2ZSCygHw Truffle的官方文档中文版 : https://pan.baidu.com/s/1y6SVd7lSLUHK21YF5FzIUQ C#区块链编程指南 : https://pan.baidu.com/s/1sJPLqp1eQqkG7jmxqwn3EA 区块链技术指南: : https://pan.baidu.com/s/13cJxAa80I6iMCczA04CZhg 精通比特币中文版: : https://pan.baidu.com/s/1lz6te3wcQuNJm28rFvBfxg Node.js区块链开发 : https://pan.baidu.com/s/1Ldpn0DvJ5LgLqwix6eWgyg geth使用指南文档中文版 : https://pan.baidu.com/s/1M0WxhmumF_fRqzt_cegnag 以太坊DApp开发环境搭建-Ubuntu : https://pan.baidu.com/s/10qL4q-uKooMehv9X2R1qSA 以太坊DApp开发环境搭建-windows : https://pan.baidu.com/s/1cyYkhIJIFuI2oyxM9Ut0eA 以太坊DApp开发私链搭建-Ubuntu : https://pan.baidu.com/s/1aBOFZT2bCjD2o0EILBWs-g 以太坊DApp开发私链搭建-windows : https://pan.baidu.com/s/10Y6F1cqUltZNN99aJv9kAA 以太坊ganache CLI命令行参数详解: https://pan.baidu.com/s/1lnknFkwenacaeM4asOcBdg 使用truflle和infura部署以太坊合约: https://pan.baidu.com/s/1PTxSVff2vHSVUihYczRRqw IPFS安装部署与开发环境搭建-windows: https://pan.baidu.com/s/1bnhDvqCoOgAqEBZXMtVbRg
区块链
2018-05-02 15:48:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
2018年各行业区块链应用白皮书的最新完整清单:游戏与虚拟现实、社交与沟通、金融、 物流与供应链、交通旅游、投资、保险、信息技术、商业与零售等。 如果你希望马上开始学习以太坊区块链应用开发,可以访问汇智网提供的出色的在线互动教程: 以太坊D智能合约与应用开发入门 以太坊去中心化电商应用实战开发
游戏与虚拟现实 VRT World The Abyss DreamTeam Sapphire Coin Virtual Universe ConcertVR Viarium VICoin Escape CryptonsGame MoneyKing Flora Flux HeroesJourney Lotus Core CEEK ChainRepublik Cryptonia Poker BestMeta VARcrypt Smart City Eco OpenPoll
区块链基础设施 XYO Network Crowd Machine Reipex RAZOOM Contract Vault eosio - EOS Root Blockchain Multiversum DAOstack cartographeum Unibright Taskfair Qompass
互联网与通信 Bubbletone World Wi-Fi Obirum JioCoin Birdchain Fobscoin
社交与沟通 DateCoin PATRON TTC Protocol ySign Cosmochain Faxport Elove LetItPlay Cultural Places 2100NEWS ADAMANT XMoneta Vanywhere SmartO Smoke Network HeartBout Oris.Space Enlte Alphabit HERO Yondo PetSource KARUNA NETWORK
市场营销 BitRewards Sharpay Nickelcoin Joint Ventures TokenSpeed Mavin Nova ClickableTV Engagement Token AdBlurb Whyral Sooloox GAGAPAY NETWORK UNIC Advertising Network AdSigma Papyrus WeevoCity
计算与数据存储 uKit AI Techspecs nCloud Forgecdn Globex SCI Market.space BaaSid Squeezer DataBrokerDAO
身份标识 Bizavest Noscam
众筹与租赁 DDToken Lendo Welltrado Gelios
供应链与物流 SKYFchain Dorado Aitheon DEEP AERO CargoCoin ZEEW
商业与零售 Omnitude GrocerCoin Eligma Ubcoin FLOGmall AKMInvest Eira Cube Rentything TrocaBit
信息技术行业 Friend Unifying Platform GraphGrail Multiven IIoT DestineyAI Quantum1Net INSPEM Path Sancoj
金融行业 TransCrypt Syntera AutoUnit Tilx Coin CriptoHub Tkeycoin L-Pesa Invox Finance Valorem foundation Korona Rubycon Nauticus Swapy Sonder Cryptohawk Stocks.Bet FIC Network Mahra Coin Privelege Coin Cartel Coin XchangeRate SOFIN Elbyte Network JIMAT COIN Cacaoshares The Deal Coin CoinOil Bitlem Network MoneyToken CoinUs Libra Credit Futereum Future Coin Light OLXA Lunes ROSCAcoin EQUI Capital Naireum Krosscoin 1WorldBlockchain Ovato Otppay Bpay.io Dinero Augmate Plaza AlibabaCoin BancCoin Profyt Pro eCoinomic.net Global Listing Exchange IGObit Taklimakan Network Bronix Raison CodeCoin GlobelBit Smart Valley FOP Coin Bitcoinus Flying Money ZPER Cryptoya SuperPay Kora Network Yoritex BitMinutes Akaiito Safein
体育行业 MyDFS Beat Staps
商业服务与咨询 Grain Payfrit RMS Synthetics Ai Enkidu Hackspace DomaniSystems Usereum VirUS Bionic Peur Basis Neuro Airclipse Iconiq Lab
房地产行业 TokenLend Intro Cher ecocity Assetereum SMARTRealty AIREN Blocksquare MUXE GENESIS Property Coin BIG Token CUBO LODGE CLUB Deedcoin
艺术音乐 Photochain \ Fenix.Cash Tune Token iShook
资产管理 Nigeria investment platform Bulleon
银行与支付 Baanx Hush
医药与健康产业 Earthmedz HPlus DeepRadiology Clinicoin HorseCoin MedicorumCoin SSOT Health BIORITMAI Yoo-Mi Zenome WELL Linda Healthcare MPLUS
教育 ExtraCredit JointEDU Disciplina
能源与设施 HydroCoin Ambit
环境服务 GMP NERA LivingOffset Modern Coin BlockGrain
休闲娱乐 ShowMeBiz SuccessLife Token Movieschain Waltix
交易所与钱包 Pointium ZANTEPAY VendEx Bithium Coal Industry Coin DigiDex Velper FIXY Network BeonBOX Sint CryptalDash Thorium FILLIT Decibels EVEN Monanex Quifas Repay.me SafeCrypt.io Yield Coin
保险 Fynance Bitrust
投资 Fast Invest WeiCrowd Globatalent IP.Gold USAT SPINDLE Ewaiter ICO HeadStart Shipowner.io Eclipse Peatcoin BullToken Moon Funding Safinus Lavenir Crypton VC Pentacore Baltic Fund
媒体 Reporter Community
挖矿 Ice Rock Mining ICO RAM Token Golden Resources MaxiMine Thorentium Russian Miner Coin Miner One Cryptosolartech Ophircoin
隐私与安全 P2PS ICloudSec Nuggets
招聘与众包 Blok Iamhero Aworker G-Global
科学研究 XSEARCH
交易 Fiancia AlphaMarket FintechBit Cryptoloans OptiToken VESA Carboneum Tesoro AllStocks Opiria Countinghouse Zuflo Traxia Fund Platform
交通 Spacekg CarVDB DRIVERO HireGo JCoin Token
旅游 TravelCoin DeskBell Chain Tripago Tuk Tuk Pass Avatara Acomobase MeetnGreetMe Cointour
区块链
2018-05-02 12:28:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
杂项
存储中状态变量的布局
静态尺寸大小的变量(除了映射和动态尺寸大小的数组类型(的其他类型变量))在存储中,是从位置0连续存储。如果可能的话,不足32个字节的多个条目被紧凑排列在一个单一的存储块,参见以下规则: 在存储块中的第一项是存储低阶对齐的。 基本类型只使用了正好存储它们的字节数。 如果一个基本类型不适合存储块的剩余部分,则移动到下一个存储块中。 结构和数组的数据总是开始一个新的块并且占整个块(根据这些规则,结构或数组项都是紧凑排列的)。
结构和数组元素是一个接着一个存储排列的,就如当初它们被声明的次序。
由于无法预知的分配的大小,映射和动态尺寸大小的数组类型(这两种类型)是使用sha3 计算来找到新的起始位置,来存放值或者数组数据。这些起始位置总是满栈块。
根据上述规则,映射或动态数组本身存放在(没有填满)的存储块位置p(或从映射到映射或数组递归应用此规则)。对于一个动态数组,存储块存储了数组元素的数目(字节数组和字符串是一个例外,见下文)。对于映射,存储块是未使用的(但它是需要的,因此,前后相邻的两个相同的映射,将使用一个不同的hash分布)。数组数据位于sha3(p), 对应于一个映射key值k位于 sha3(k . p)  (这里 . 是连接符)。如果该值又是一个非基本类型,位置的偏移量是sha3(k . p)。
如果bytes 和 string是短类型的,它们将和其长度存储在同一个存储块里。特别是:如果数据最长31字节,它被存储在高阶字节(左对齐), 低字节存储length* 2。 如果是长类型,主存储块存储 length* 2 + 1,  数据存储在sha3(shot)。
因此,本合约片段如下: contract c {   struct S { uint a; uint b; }   uint x;   mapping(uint => mapping(uint => S)) data; }
深奥的特点
在Solidity的类型系统中,有一些在语法中没有对应的类型。其中就有函数的类型。但若使用 var (这个关键字),该函数就被认为是这个类型的局部变量: contract FunctionSelector {   function select(bool useB, uint x) returns (uint z) {     var f = a;     if (useB) f = b;     return f(x);   }   function a(uint x) returns (uint z) {     return x * x;   }   function b(uint x) returns (uint z) {     return 2 * x;   } }
(在上面的程序片段中)
若调用select(false, x),  就会计算 x * x 。若调用select(true, x))就会计算 2 * x。
内部-优化器
Solidity 优化器是在汇编级别上的操作,所以它也可以同时被其他语言所使用。它将指令的(执行)次序,在JUMP 和 JUMPDEST上分成基本的块。在这些块中,指令被解析 。 堆栈、内存或存储上的每一次修改,都将作为表达式被记录。该表达式包括一条指令以及指向其他表达式的一系列参数的一个指针。现在的主要意思是要找到相等的表达式(在每次输入),做成了表达式的类。优化器首先在已知的表达式列表里找,若找不到的话,就根据constant + constant = sum_of_constants  或 X * 1 = X  来简化。 因为这样做是递归的,如果第二个因子是一个更复杂的表达式,我们也可以应用latter规则来计算。存储和内存位置的修改,是不知道存储和内存的位置的区别。如果我们先写到的位置x,再写到位置y , x,y均是输入变量。第二个可以覆写第一个,所以我们不知道x是存放在y之后的。另一方面,如果一个简化的表达式 x-y 能计算出一个非零常数,我们就知道x存放的内容。
在这个过程结束时,我们知道,表达式必须在堆栈中结尾,并有一系列对内存和存储的修改。这些信息存储在block上,并链接这些block。此外,有关堆栈,存储和内存配置的信息会转发到下一个block。如果我们知道所有的 JUMP 和 JUMPI 指令,我们可以建立一个完整的程序控制流图。如果我们不知道目标块(原则上,跳转目标是从输入里得到的),我们必须清除所有输入状态的存储块上的信息,(因为它的目标块未知)。如果条件计算的结果为一个常量,它转化为一个无条件jump。
作为最后一步,在每个块中的代码完全可以重新生成。从堆栈里block的结尾表达式开始,创建一个依赖关系图。每个不是这个图上的操作将舍弃。现在能按照原来代码的顺序,生成对存储和内存修改的代码(舍弃不必要的修改)。最后,在正确位置的堆栈上,生成所有的值。
这些步骤适用于每一个基本块, 如果它是较小的,用新生成的代码来替换。如果一个基本块在JUMPI上进行分割,在分析过程中,条件表达式的结果计算为一个常数,JUMP就用常量值进行替换。代码如下 var x = 7; data[7] = 9; if (data[x] != x + 2)   return 2; else   return 1;
简化成下面可以编译的形式 data[7] = 9; return 1;
即使在开始处包含有jump指令
使用命令行编译器
一个 Solidity 库构建的目标是solc, Solidity命令行编译器。使用solc –help  为您提供所有选项的解释。编译器可以产生不同的输出,从简单的二进制文件,程序集的抽象语法树(解析树)到gas使用的估量。如果你只想编译一个文件,你运行solc –bin sourceFile.sol , 将会打印出二进制。你部署你的合约之前,使用solc –optimize –bin sourceFile.sol 来激活优化器。如果你想获得一些solc更进一步的输出变量,可以使用solc -o outputDirectory –bin –ast –asm sourceFile.sol,(这条命令)将通知编译器输出结果到单独的文件中。
命令行编译器会自动从文件系统中读取输入文件,但也可以如下列方法,提供重定向路径prefix=path :
solc github.com/ethereum/dapp-bin/=/usr/local/lib/dapp-bin/ =/usr/local/lib/fallback file.sol
该命令告诉编译器在/usr/local/lib/dapp-bin目录下,寻找以github.com/ethereum/dapp-bin/  开头的文件,如果找不到的话,到usr/local/lib/fallback目录下找(空前缀总是匹配)。
solc不会从remapping目标的外部,或者显式定义的源文件的外部文件系统读取文件,所以要写成 import “/etc/passwd”;     只有增加 =/ 作为remapping,程序才能工作。
如果remapping里找到了多个匹配,则选择有共同的前缀最长的那个匹配。
如果你的合约使用了  libraries ,你会注意到字节码中包含了 form LibraryName 这样的子字符串。你可以在这些地方使用solc 作为链接器,来插入库地址 :
Either add –libraries “Math:0x12345678901234567890 Heap:0xabcdef0123456”    提供每个库的地址, 或者在文件中存放字符串(每行一个库)
然后运行solc,后面写 –libraries fileName.
如果solc后面接着 –link 选项,所有输入文件将被解释为未链接的二进制文件(十六进制编码), LibraryName形式如前所述, 库此时被链接(从stdin读取输入,从stdout输出)。在这种情况下,除了–libraries,其他所有的选项都将被忽略(包括 -o)
提示和技巧 在数组中使用delete,就是删除数组中的所有元素。 使用较短的类型和结构元素,短类型分组在一起进行排序。sstore操作可能合并成一个单一的sstore,这可以降低gas的成本(sstore消耗5000或20000 gas,所以这是你必须优化的原因)。使用天gas的价格估算功能(优化器 enable)进行检查! 让你的状态变量公开,编译器会免费创建 getters 。 如果你结束了输入或状态的检查条件,请尝试使用函数修饰符。 如果你的合约有一个功能send, 但你想使用内置的send功能,请使用 address(contractVariable).send(amount)。 如果你不想你的合约通过send接收ether,您可以添加一个抛出回退函数 function() { throw; }.。 用单条赋值语句初始化存储结构:x = MyStruct({a: 1, b: 2});
陷阱
不幸的是,还有一些编译器微妙的情况还没有告诉你。 在for (var i = 0; i < arrayName.length; i++) { ... },  i的类型是uint8,因为这是存放值0最小的类型。如果数组元素超过255个,则循环将不会终止。
列表
全局变量 block.coinbase (address):当前块的矿场的地址 block.difficulty (uint):当前块的难度 block.gaslimit (uint):当前块的gaslimit block.number (uint):当前块的数量 block.blockhash (function(uint) returns (bytes32)):给定的块的hash值, 只有最近工作的256个块的hash值 block.timestamp (uint):当前块的时间戳 msg.data (bytes):完整的calldata msg.gas (uint): 剩余gas msg.sender (address):消息的发送者(当前调用) msg.value (uint):和消息一起发送的wei的数量 now (uint):当前块的时间戳(block.timestamp的别名) tx.gasprice (uint):交易的gas价格 tx.origin (address):交易的发送者(全调用链) sha3(...) returns (bytes32):计算(紧凑排列的)参数的 Ethereum-SHA3  hash值  sha256(...) returns (bytes32)计算(紧凑排列的)参数的SHA256 hash值  ripemd160(...) returns (bytes20):计算 256个(紧凑排列的)参数的RIPEMD ecrecover(bytes32, uint8, bytes32, bytes32) returns (address):椭圆曲线签名公钥恢复 addmod(uint x, uint y, uint k) returns (uint):计算(x + y)K,加法为任意精度,不以2 ** 256取余 mulmod(uint x, uint y, uint k) returns (uint):计算(XY)K,乘法为任意精度,不以2 * 256取余 this (current contract’s type): 当前合约,在地址上显式转换 super:在层次关系上一层的合约 selfdestruct(address):销毁当前的合同,将其资金发送到指定address地址
.balance:address地址中的账户余额(以wei为单位)
.send(uint256) returns (bool):将一定量wei发送给address地址,若失败返回false。
函数可见性定义符 function myFunction()  returns (bool) {     return true; } public:在外部和内部均可见(创建存储/状态变量的访问者函数) private:仅在当前合约中可见 external: 只有外部可见(仅对函数)- 仅仅在消息调用中(通过this.fun) internal: 只有内部可见
Modifiers constant for state variables: Disallows assignment (except initialisation), does not occupy storage slot. constant for functions: Disallows modification of state - this is not enforced yet. anonymous for events: Does not store event signature as topic. indexed for event parameters: Stores the parameter as topic.
修饰符 constant for state variables: 不允许赋值(除了初始化),不占用存储块。 constant for functions:不允许改变状态- 这个目前不是强制的。 anonymous for events:不能将topic作为事件指纹进行存储。 indexed for event parameters: 将topic作为参数存储。
区块链
2018-04-30 21:30:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
智能合约
Solidity里的智能合约是面向对象语言里的类。它们持久存放在状态变量和函数中,(在里面)可以通过solidity修改这些变量。在不同的智能合约(实例)中调用一个函数(的过程),(实际上)是在EVM(Ether虚拟机)中完成一次调用,并且完成(一次)上下文切换,(此时)状态变量是不可访问的。
创建合约
      合约可以从“外部”创建,也可以由Solidity合约创立。在创建合约时,它的构造函数(函具有与合约名称同名的函数)将被执行。
   web3.js,即  JavaScript API, 是这样做的: // The json abi array generated by the compiler var abiArray = [   {     "inputs":[       {"name":"x","type":"uint256"},       {"name":"y","type":"uint256"}     ],     "type":"constructor"   },   {     "constant":true,     "inputs":[],     "name":"x",     "outputs":[{"name":"","type":"bytes32"}],     "type":"function"   } ]; var MyContract = web3.eth.contract(abiArray);// deploy new contractvar contractInstance = MyContract.new(   10,   {from: myAccount, gas: 1000000} ); // The json abi array generated by the compiler  由编译器生成的json abi 数组 var abiArray = [   {     "inputs":[       {"name":"x","type":"uint256"},       {"name":"y","type":"uint256"}     ],     "type":"constructor"   },   {     "constant":true,     "inputs":[],     "name":"x",     "outputs":[{"name":"","type":"bytes32"}],     "type":"function"   } ]; var MyContract = web3.eth.contract(abiArray); // deploy new contract  部署一个新合约 var contractInstance = MyContract.new(   10,   {from: myAccount, gas: 1000000} );
在内部,在合约的代码后要接着有构造函数的参数,但如果你使用web3.js,就不必关心这个。
如果是一个合约要创立另外一个合约,被创立的合约的源码(二进制代码)要能被创立者知晓。这意味着:循环创建依赖就成为不可能的事情。 contract OwnedToken {     // TokenCreator is a contract type that is defined below.     // It is fine to reference it as long as it is not used     // to create a new contract.     TokenCreator creator;     address owner;     bytes32 name;     // This is the constructor which registers the     // creator and the assigned name.     function OwnedToken(bytes32 _name) {         owner = msg.sender;         // We do an explicit type conversion from `address`         // to `TokenCreator` and assume that the type of         // the calling contract is TokenCreator, there is         // no real way to check that.         creator = TokenCreator(msg.sender);         name = _name;     }     function changeName(bytes32 newName) {         // Only the creator can alter the name --         // the comparison is possible since contracts         // are implicitly convertible to addresses.         if (msg.sender == creator) name = newName;     }     function transfer(address newOwner) {         // Only the current owner can transfer the token.         if (msg.sender != owner) return;         // We also want to ask the creator if the transfer         // is fine. Note that this calls a function of the         // contract defined below. If the call fails (e.g.         // due to out-of-gas), the execution here stops         // immediately.         if (creator.isTokenTransferOK(owner, newOwner))             owner = newOwner;     }} contract TokenCreator {     function createToken(bytes32 name)        returns (OwnedToken tokenAddress)     {         // Create a new Token contract and return its address.         // From the JavaScript side, the return type is simply         // "address", as this is the closest type available in         // the ABI.         return new OwnedToken(name);     }     function changeName(OwnedToken tokenAddress, bytes32 name) {         // Again, the external type of "tokenAddress" is         // simply "address".         tokenAddress.changeName(name);     }     function isTokenTransferOK(         address currentOwner,         address newOwner     ) returns (bool ok) {         // Check some arbitrary condition.         address tokenAddress = msg.sender;         return (sha3(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);     }} contract OwnedToken {     // TokenCreator is a contract type that is defined below.  TokenCreator是在下面定义的合约类型     // It is fine to reference it as long as it is not used  若它本身不用于创建新的合约的话,它就是一个引用     // to create a new contract.     TokenCreator creator;     address owner;     bytes32 name;     // This is the constructor which registers the 这个是一个登记创立者和分配名称的结构函数     // creator and the assigned name.     function OwnedToken(bytes32 _name) {         owner = msg.sender;         // We do an explicit type conversion from `address` 我们做一次由`address`到`TokenCreator` 的显示类型转换,,确保调用合约的类型是 TokenCreator, (因为没有真正的方法来检测这一点)         // to `TokenCreator` and assume that the type of         // the calling contract is TokenCreator, there is         // no real way to check that.         creator = TokenCreator(msg.sender);         name = _name;     }     function changeName(bytes32 newName) {         // Only the creator can alter the name --  仅仅是创立者可以改变名称--         // the comparison is possible since contracts  因为合约是隐式转换到地址上,这种比较是可能的         // are implicitly convertible to addresses.         if (msg.sender == creator) name = newName;     }     function transfer(address newOwner) {         // Only the current owner can transfer the token.  仅仅是 仅仅是当前(合约)所有者可以转移 token         if (msg.sender != owner) return;         // We also want to ask the creator if the transfer  我们可以询问(合约)创立者"转移是否成功"         // is fine. Note that this calls a function of the  注意下面定义的合约的函数调用         // contract defined below. If the call fails (e.g.     如果函数调用失败,(如gas用完了等原因)         // due to out-of-gas), the execution here stops  程序的执行将立刻停止         // immediately.         if (creator.isTokenTransferOK(owner, newOwner))             owner = newOwner;     }} contract TokenCreator {     function createToken(bytes32 name)        returns (OwnedToken tokenAddress)     {         // Create a new Token contract and return its address.  创立一个新的Token合约,并且返回它的地址         // From the JavaScript side, the return type is simply  从 JavaScript观点看,返回的地址类型是"address"         // "address", as this is the closest type available in   这个是和ABI最接近的类型         // the ABI.         return new OwnedToken(name);     }     function changeName(OwnedToken tokenAddress, bytes32 name) {         // Again, the external type of "tokenAddress" is     "tokenAddress" 的外部类型也是 简单的"address".         // simply "address".         tokenAddress.changeName(name);     }     function isTokenTransferOK(         address currentOwner,         address newOwner     ) returns (bool ok) {         // Check some arbitrary condition. 检查各种条件         address tokenAddress = msg.sender;         return (sha3(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);     } }
可见性和访问限制符
因为Solidity可以理解两种函数调用(“内部调用”,不创建一个真实的EVM调用(也称为“消息调用”);“外部的调用”-要创建一个真实的EMV调用),  有四种的函数和状态变量的可见性。
函数可以被定义为external, public, internal or private,缺省是 public。对状态变量而言, external是不可能的,默认是 internal。
external:  外部函数是合约接口的一部分,这意味着它们可以从其他合约调用, 也可以通过事务调用。外部函数f不能被内部调用(即 f()不执行,但this.f()执行)。外部函数,当他们接收大数组时,更有效。
**public:**公共函数是合约接口的一部分,可以通过内部调用或通过消息调用。对公共状态变量而言,会有的自动访问限制符的函数生成(见下文)。
**internal:**这些函数和状态变量只能内部访问(即在当前合约或由它派生的合约),而不使用(关键字)this 。
private :私有函数和状态变量仅仅在定义该合约中可见, 在派生的合约中不可见。
请注意
在外部观察者中,合约的内部的各项均可见。 用 private  仅仅防止其他合约来访问和修改(该合约中)信息, 但它对blockchain之外的整个世界仍然可见。
可见性说明符是放在在状态变量的类型之后,(也可以放在)参数列表和函数返回的参数列表之间。 contract c {     function f(uint a) private returns (uint b) { return a + 1; }     function setData(uint a) internal { data = a; }     uint public data; }
其他合约可以调用c.data()来检索状态存储中data的值,但不能访问(函数)f。由c派生的合约可以访问(合约中)setData(函数),以便改变data的值(仅仅在它们自己的范围里)。
访问限制符函数
编译器会自动创建所有公共状态变量的访问限制符功能。下文中的合约中有一个称作data的函数,它不带任何参数的,它返回一个uint类型,  状态变量的值是data。可以在声明里进行状态变量的初始化。
访问限制符函数有外部可见性。如果标识符是内部可访问(即没有this),则它是一个状态变量,如果外部可访问的(即 有this),则它是一个函数。 contract test {     uint public data = 42;}
下面的例子复杂些: contract complex {     struct Data { uint a; bytes3 b; mapping(uint => uint) map; }     mapping(uint => mapping(bool => Data[])) public data;}
它生成了如下形式的函数: function data(uint arg1, bool arg2, uint arg3) returns (uint a, bytes3 b){     a = data[arg1][arg2][arg3].a;     b = data[arg1][arg2][arg3].b;}
注意 结构体的映射省略了,因为没有好的方法来提供映射的键值。
函数修饰符
修饰符可以用来轻松改变函数的行为, 例如,在执行的函数之前自动检查条件。他们是可继承合约的属性,也可被派生的合约重写。 contract owned {     function owned() { owner = msg.sender; }     address owner;     // This contract only defines a modifier but does not use     // it - it will be used in derived contracts.     // The function body is inserted where the special symbol     // "_" in the definition of a modifier appears.     // This means that if the owner calls this function, the     // function is executed and otherwise, an exception is     // thrown.     modifier onlyowner { if (msg.sender != owner) throw; _ }}contract mortal is owned {     // This contract inherits the "onlyowner"-modifier from     // "owned" and applies it to the "close"-function, which     // causes that calls to "close" only have an effect if     // they are made by the stored owner.     function close() onlyowner {         selfdestruct(owner);     }}contract priced {     // Modifiers can receive arguments:     modifier costs(uint price) { if (msg.value >= price) _ }}contract Register is priced, owned {     mapping (address => bool) registeredAddresses;     uint price;     function Register(uint initialPrice) { price = initialPrice; }     function register() costs(price) {         registeredAddresses[msg.sender] = true;     }     function changePrice(uint _price) onlyowner {         price = _price;     }} contract owned {     function owned() { owner = msg.sender; }     address owner;     // This contract only defines a modifier but does not use 这个合约仅仅定义了修饰符,但没有使用它     // it - it will be used in derived contracts. 在派生的合约里使用     // The function body is inserted where the special symbol  ,函数体插入到特殊的标识 "_"定义的地方      // "_" in the definition of a modifier appears.     // This means that if the owner calls this function, the 这意味着若它自己调用此函数,则函数将被执行     // function is executed and otherwise, an exception is 否则,一个异常将抛出     // thrown.     modifier onlyowner { if (msg.sender != owner) throw; _ } } contract mortal is owned {     // This contract inherits the "onlyowner"-modifier from   该合约是从"owned" 继承的"onlyowner"修饰符,     // "owned" and applies it to the "close"-function, which    并且应用到"close"函数, 如果他们存储owner     // causes that calls to "close" only have an effect if       // they are made by the stored owner.     function close() onlyowner {         selfdestruct(owner);     } } contract priced {     // Modifiers can receive arguments:  修饰符可以接收参数     modifier costs(uint price) { if (msg.value >= price) _ } } contract Register is priced, owned {     mapping (address => bool) registeredAddresses;     uint price;     function Register(uint initialPrice) { price = initialPrice; }     function register() costs(price) {         registeredAddresses[msg.sender] = true;     }     function changePrice(uint _price) onlyowner {         price = _price;     } }
多个修饰符可以被应用到一个函数中(用空格隔开),并顺序地进行计算。当离开整个函数时,显式返回一个修饰词或函数体,  同时在“_”之后紧接着的修饰符,直到函数尾部的控制流,或者是修饰体将继续执行。任意表达式允许修改参数,在修饰符中,所有函数的标识符是可见的。在此函数由修饰符引入的标识符是不可见的(虽然他们可以通过重写,改变他们的值)。
常量
状态变量可以声明为常量(在数组和结构体类型上仍然不可以这样做,映射类型也不可以)。 contract C {     uint constant x = 32*\*22 + 8;     string constant text = "abc"; }
编译器不保留这些变量存储块,  每到执行到这个语句时,常量值又被替换一次。
表达式的值只能包含整数算术运算。
回退函数
一个合约可以有一个匿名函数。若没有其他函数和给定的函数标识符一致的话,该函数将没有参数,将执行一个合约的调用(如果没有提供数据)。
此外,当合约接收一个普通的Ether时,函数将被执行(没有数据)。在这样一个情况下,几乎没有gas用于函数调用,所以调用回退函数是非常廉价的,这点非常重要。 contract Test {     function() { x = 1; }     uint x;} //  This contract rejects any Ether sent to it. It is good // practise to  include such a function for every contract // in order not to loose  Ether. contract Rejector {     function() { throw; } } contract Caller {   function callTest(address testAddress) {       Test(testAddress).call(0xabcdef01); // hash does not exist       // results in Test(testAddress).x becoming == 1.       Rejector r = Rejector(0x123);       r.send(2 ether);       // results in r.balance == 0   } } contract Test {     function() { x = 1; }     uint x;} //  This contract rejects any Ether sent to it. It is good   这个合约拒绝任何发给它的Ether. // practise to  include such a function for every contract  为了严管Ether,在每个合约里包含一个这样的函数,是非常好的做法 // in order not to loose  Ether. contract Rejector {     function() { throw; } } contract Caller {   function callTest(address testAddress) {       Test(testAddress).call(0xabcdef01); // hash does not exist hash值不存在       // results in Test(testAddress).x becoming == 1.  Test(testAddress).x的结果  becoming == 1       Rejector r = Rejector(0x123);       r.send(2 ether);       // results in r.balance == 0     结果里r.balance == 0     } }
事件
事件允许EMV写日志功能的方便使用, 进而在dapp的用户接口中用JavaScript顺序调用,从而监听这些事件。
事件是合约中可继承的成员。当他们调用时,会在导致一些参数在事务日志上的存储--在blockchain上的一种特殊的数据结构。这些日志和合约的地址相关联,  将被纳入blockchain中,存储在block里以便访问( 在 Frontier  和** Homestead 里是永久存储,但在 Serenity**里有些变化)。在合约内部,日志和事件数据是不可访问的(从创建该日志的合约里)。
SPV日志证明是可行的, 如果一个外部实体提供一个这样的证明给合约,  它可以检查blockchain内实际存在的日志(但要注意这样一个事实,最终要提供block的headers, 因为合约只能看到最近的256块hash值)。
最多有三个参数可以接收属性索引,它将对各自的参数进行检索:  可以对用户界面中的索引参数的特定值进行过滤。
如果数组(包括string和 bytes)被用作索引参数,  就会以sha3-hash形式存储,而不是topic。
除了用anonymous声明事件之外,事件的指纹的hash值都将是topic之一。这意味着,不可能通过名字来过滤特定的匿名事件。
所有非索引参数将被作为数据日志记录的一部分进行存储。 contract ClientReceipt {     event Deposit(         address indexed _from,         bytes32 indexed _id,         uint _value     );     function deposit(bytes32 _id) {         // Any call to this function (even deeply nested) can         // be detected from the JavaScript API by filtering         // for `Deposit` to be called.         Deposit(msg.sender, _id, msg.value);     } } contract ClientReceipt {     event Deposit(         address indexed _from,         bytes32 indexed _id,         uint _value     );     function deposit(bytes32 _id) {         // Any call to this function (even deeply nested) can 任何对这个函数的调用都能通过JavaScipt API , 用`Deposit` 过滤来检索到(即使深入嵌套)         // be detected from the JavaScript API by filtering         // for `Deposit` to be called.         Deposit(msg.sender, _id, msg.value);     } }
JavaScript API 的使用如下: var abi = /\ abi as generated by the compiler /; var ClientReceipt = web3.eth.contract(abi); var clientReceipt = ClientReceipt.at(0x123 /\ address /); var event = clientReceipt.Deposit(); // watch for changes event.watch(function(error, result){     // result will contain various information     // including the argumets given to the Deposit     // call.     if (!error)         console.log(result);}); // Or pass a callback to start watching immediately var event = clientReceipt.Deposit(function(error, result) {     if (!error)         console.log(result); }); var abi = /\ abi as generated by the compiler /;     /\ 由编译器生成的abi /;   var ClientReceipt = web3.eth.contract(abi); var clientReceipt = ClientReceipt.at(0x123 /\ address /);   /\ 地址 /);  var event = clientReceipt.Deposit(); // watch for changes   观察变化 event.watch(function(error, result){     // result will contain various information   结果包含不同的信息: 包括给Deposit调用的参数     // including the argumets given to the Deposit     // call.     if (!error)         console.log(result);}); // Or pass a callback to start watching immediately 或者通过callback立刻开始观察 var event = clientReceipt.Deposit(function(error, result) {     if (!error)         console.log(result); });
底层日志的接口
还可以通过函数log0 log1,log2,log3 log4到 logi,共i+1个bytes32类型的参数来访问底层日志机制的接口。第一个参数将用于数据日志的一部分,其它的参数将用于topic。上面的事件调用可以以相同的方式执行。. log3(     msg.value,     0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,     msg.sender,     _id );
很长的十六进制数等于
sha3(“Deposit(address,hash256,uint256)”), 这个就是事件的指纹。
理解事件的额外的资源 Javascipt文档 事件的用法举例 如何在js中访问
继承
通过包括多态性的复制代码,Solidity支持多重继承。
除非合约是显式给出的,所有的函数调用都是虚拟的,绝大多数派生函数可被调用。
即使合约是继承了多个其他合约, 在blockchain上只有一个合约被创建,  基本合约代码总是被复制到最终的合约上。
通用的继承机制非常类似于 Python 里的继承,特别是关于多重继承方面。
下面给出了详细的例子。 contract owned {     function owned() { owner = msg.sender; }     address owner;} // Use "is" to derive from another contract. Derived// contracts can access all non-private members including// internal functions and state variables. These cannot be// accessed externally via `this`, though.contract mortal is owned {     function kill() {         if (msg.sender == owner) selfdestruct(owner);     }} // These abstract contracts are only provided to make the// interface known to the compiler. Note the function// without body. If a contract does not implement all// functions it can only be used as an interface.contract Config {     function lookup(uint id) returns (address adr);}contract NameReg {     function register(bytes32 name);     function unregister();  } // Multiple inheritance is possible. Note that "owned" is// also a base class of "mortal", yet there is only a single// instance of "owned" (as for virtual inheritance in C++).contract named is owned, mortal {     function named(bytes32 name) {         Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);         NameReg(config.lookup(1)).register(name);     }     // Functions can be overridden, both local and     // message-based function calls take these overrides     // into account.     function kill() {         if (msg.sender == owner) {             Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);             NameReg(config.lookup(1)).unregister();             // It is still possible to call a specific             // overridden function.             mortal.kill();         }     }} // If a constructor takes an argument, it needs to be// provided in the header (or modifier-invocation-style at// the constructor of the derived contract (see below)).contract PriceFeed is owned, mortal, named("GoldFeed") {    function updateInfo(uint newInfo) {       if (msg.sender == owner) info = newInfo;    }    function get() constant returns(uint r) { return info; }    uint info; } contract owned {     function owned() { owner = msg.sender; }     address owner;} // Use "is" to derive from another contract. Derived   用"is"是从其他的合约里派生出 // contracts can access all non-private members including   派生出的合约能够访问所有非私有的成员,包括内部函数和状态变量。  它们不能从外部用'this'来访问。 // internal functions and state variables. These cannot be // accessed externally via `this`, though. contract mortal is owned {     function kill() {         if (msg.sender == owner) selfdestruct(owner);     }} // These abstract contracts are only provided to make the  这些抽象的合约仅仅是让编译器知道已经生成了接口, // interface known to the compiler. Note the function   注意:函数没有函数体。如果合约不做实现的话,它就只能当作接口。 // without body. If a contract does not implement all // functions it can only be used as an interface. contract Config {     function lookup(uint id) returns (address adr); } contract NameReg {     function register(bytes32 name);     function unregister();  } // Multiple inheritance is possible. Note that "owned" is 多重继承也是可以的,注意"owned" 也是mortal的基类, 虽然 仅仅有"owned"的单个实例,(和C++里的virtual继承一样) // also a base class of "mortal", yet there is only a single // instance of "owned" (as for virtual inheritance in C++). contract named is owned, mortal {     function named(bytes32 name) {         Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);         NameReg(config.lookup(1)).register(name);     }     // Functions can be overridden, both local and  函数被重写,本地和基于消息的函数调用把这些override带入账户里。     // message-based function calls take these overrides     // into account.     function kill() {         if (msg.sender == owner) {             Config config = Config(0xd5f9d8d94886e70b06e474c3fb14fd43e2f23970);             NameReg(config.lookup(1)).unregister();             // It is still possible to call a specific  还可以调用特定的override函数             // overridden function.             mortal.kill();         }     }} // If a constructor takes an argument, it needs to be  如果构造器里带有一个参数,有必要在头部给出,(或者在派生合约的构造器里使用修饰符调用方式modifier-invocation-style(见下文)) // provided in the header (or modifier-invocation-style at // the constructor of the derived contract (see below)). contract PriceFeed is owned, mortal, named("GoldFeed") {    function updateInfo(uint newInfo) {       if (msg.sender == owner) info = newInfo;    }    function get() constant returns(uint r) { return info; }    uint info; }
注意:在上文中,我们使用mortal.kill() 来“forward” 析构请求。这种做法是有问题的,请看下面的例子: contract mortal is owned {     function kill() {         if (msg.sender == owner) selfdestruct(owner);     } } contract Base1 is mortal {     function kill() { /\ do cleanup 1  清除1 / mortal.kill();  } } contract Base2 is mortal {     function kill() { /\ do cleanup 2  清除2 / mortal.kill();  } } contract Final is Base1, Base2 { }
 Final.kill() 将调用Base2.kill作为最后的派生重写,但这个函数绕开了Base1.kill。因为它不知道有Base1。这种情况下要使用 super contract mortal is owned {     function kill() {         if (msg.sender == owner) selfdestruct(owner);     } } contract Base1 is mortal {     function kill() { /\ do cleanup 1   清除1  \/    super.kill(); } } contract Base2 is mortal {     function kill() { /\ do cleanup 2  清除2  \/    super.kill(); } } contract Final is Base2, Base1 { }
若Base1 调用了super函数,它不是简单地调用基本合约之一的函数, 它是调用最后继承关系的下一个基本合约的函数。所以它会调用 base2.kill()(注意,最后的继承顺序是–从最后的派生合约开始:Final, Base1, Base2, mortal, owned)。当使用类的上下文中super不知道的情况下,真正的函数将被调用,虽然它的类型已经知道。这个和普通的virtual方法的查找相似。
基本构造函数的参数
派生的合约需要为基本构造函数提供所有的参数。这可以在两处进行: contract Base {     uint x;     function Base(uint _x) { x = _x;} } contract Derived is Base(7) {     function Derived(uint _y) Base(_y * _y) {     } }
第一种方式是直接在继承列表里实现(是 Base(7)),第二种方式是在派生的构造器的头部,修饰符被调用时实现(Base(_y * _y))。如果构造函数参数是一个常量,并且定义了合约的行为或描述了它的行为,第一种方式比较方便。 如果基本构造函数参数依赖于派生合约的构造函数,则必须使用第二种方法。如果在这个荒谬的例子中,这两个地方都被使用,修饰符样式的参数优先。
多继承和线性化
允许多重继承的编程语言,要处理这样几个问题,其中一个是 Diamond 问题。Solidity是沿用Python的方式, 使用“ C3 线性化”,在基类的DAG强制使用特定的顺序。这导致单调但不允许有一些的继承关系。特别是,在其中的基础类的顺序是直接的,这点非常重要。在下面的代码中,Solidity会报错:“继承关系的线性化是不可能的”。
contract  X {}
contract  A is X {}
contract  C is A, X {}
这个原因是,C要求X来重写A(定义A,X这个顺序),但A本身的要求重写X,这是一个矛盾,不能解决。
一个简单的规则是要指定基类中的顺序,从“最基本”到“最近派生”。
抽象契约
合约函数可以缺少实现(请注意,函数声明头将被终止),见下面的例子: contract feline {     function utterance() returns (bytes32); }
这样的合约不能被编译(即使它们包含实现的函数和非实现的函数),但它们可以用作基本合约: contract Cat is feline {     function utterance() returns (bytes32) { return "miaow"; } }
如果一个合约是从抽象合约中继承的,而不实现所有非执行功能,则它本身就是抽象的。

库和合约类似,但是它们的目的主要是在给定地址上部署,以及用EVM的CALLCODE特性来重用代码。这些代码是在调用合约的上下文里执行的,例如调用合约的指针和调用合约的存储能够被访问。由于库是一片独立的代码,如果它们显示地提供的话,就仅仅能访问到调用合约的状态变量(有方法命名它们)
下面的例子解释了怎样使用库(确保用 using for 来实现) library Set {   // We define a new struct datatype that will be used to   我们定义了一个新的结构体数据类型,用于存放调用合约中的数据   // hold its data in the calling contract.   struct Data { mapping(uint => bool) flags; }   // Note that the first parameter is of type "storage 注意第一个参数是 “存储引用”类型,这样仅仅是它的地址,而不是它的内容在调用中被传入 这是库函数的特点,    // reference" and thus only its storage address and not   // its contents is passed as part of the call.  This is a   // special feature of library functions.  It is idiomatic  若第一个参数用"self"调用时很笨的的,如果这个函数可以被对象的方法可见。   // to call the first parameter 'self', if the function can   // be seen as a method of that object.   function insert(Data storage self, uint value)       returns (bool)   {       if (self.flags[value])           return false; // already there 已经在那里       self.flags[value] = true;       return true;   }   function remove(Data storage self, uint value)       returns (bool)   {       if (!self.flags[value])           return false; // not there 不在那里       self.flags[value] = false;       return true;   }   function contains(Data storage self, uint value)       returns (bool)   {       return self.flags[value];   } } contract C {     Set.Data knownValues;     function register(uint value) {         // The library functions can be called without a  这个库函数没有特定的函数实例被调用,因为“instance”是当前的合约         // specific instance of the library, since the         // "instance" will be the current contract.         if (!Set.insert(knownValues, value))             throw;     }     // In this contract, we can also directly access knownValues.flags, if we want 在这个合约里,如果我们要的话,也可以直接访问 knownValues.flags .*}
当然,你不必这样使用库--他们也可以事前不定义结构体数据类型,就可以使用。 没有任何存储引入参数,函数也可以执行。也可以在任何位置,有多个存储引用参数。
Set.contains, Set.insert and Set.remove都可编译到(CALLCODE)外部合约/库。如果你使用库,注意真正进行的外部函数调用,所以`msg.sender不再指向来源的sender了,而是指向了正在调用的合约。msg.value包含了调用库函数中发送的资金。
因为编译器不知道库将部署在哪里。这些地址不得不由linker填进最后的字节码(见 使用命令行编译器 如何使用命令行编译器链接)。如果不给编译器一个地址做参数,编译的十六进制码就会包含__Set __这样的占位符(Set是库的名字)。通过替换所有的40个字符的十六进制编码的库合约的地址,地址可以手动进行填充。
比较合约和库的限制: 无状态变量 不能继承或被继承
(这些可能在以后会被解除)
库的常见“坑”
msg.sender的值
msg.sender的值将是调用库函数的合约的值。
例如,如果A调用合约B,B内部调用库C。在库C库的函数调用里,msg.sender将是合约B的地址。
表达式LibraryName.functionName() 用CALLCODE完成外部函数调用, 它映射到一个真正的EVM调用,就像otherContract.functionName() 或者 this.functionName()。这种调用可以一级一级扩展调用深度(最多1024级),把msg.sender存储为当前的调用者,然后执行库合约的代码,而不是执行当前的合约存储。这种执行方式是发生在一个完全崭新的内存环境中,它的内存类型将被复制,并且不能绕过引用。
转移Ether
原则上使用LibraryName.functionName.value(x)()来转移Ether。但若使用CALLCODE,Ether会在当前合约里用完。
Using For
指令 using A for B;  可用于附加库函数(从库A)到任何类型(B)。这些函数将收到一个作为第一个参数的对象(像Python中self变量)。
using A for *;,是指函数从库A附加到任何类型。
在这两种情况下,所有的函数将被附加,(即使那些第一个参数的类型与对象的类型不匹配)。该被调用函数的入口类型将被检查,并进行函数重载解析。
using A for B; 指令在当前的范围里是有效的,作用范围限定在现在的合约里。但(出了当前范围)在全局范围里就被移除。因此,通过 including一个模块,其数据类型(包括库函数)都将是可用的,而不必添加额外的代码。
让我们用这种方式重写库中的set示例: // This is the same code as before, just without comments library Set {   struct Data { mapping(uint => bool) flags; }   function insert(Data storage self, uint value)       returns (bool)   {       if (self.flags[value])         return false; // already there       self.flags[value] = true;       return true;   }   function remove(Data storage self, uint value)       returns (bool)   {       if (!self.flags[value])           return false; // not there       self.flags[value] = false;       return true;   }   function contains(Data storage self, uint value)       returns (bool)   {       return self.flags[value];   } } contract C {     using Set for Set.Data; // this is the crucial change     Set.Data knownValues;     function register(uint value) {         // Here, all variables of type Set.Data have         // corresponding member functions.         // The following function call is identical to         // Set.insert(knownValues, value)         if (!knownValues.insert(value))             throw;     } } // This is the same code as before, just without comments   这个代码和之前的一样,仅仅是没有注释 library Set {   struct Data { mapping(uint => bool) flags; }   function insert(Data storage self, uint value)       returns (bool)   {       if (self.flags[value])         return false; // already there 已经在那里       self.flags[value] = true;       return true;   }   function remove(Data storage self, uint value)       returns (bool)   {       if (!self.flags[value])           return false; // not there 没有       self.flags[value] = false;       return true;   }   function contains(Data storage self, uint value)       returns (bool)   {       return self.flags[value];   } } contract C {     using Set for Set.Data; // this is the crucial change   这个是关键的变化     Set.Data knownValues;     function register(uint value) {         // Here, all variables of type Set.Data have   这里,所有Set.Data 的变量都有相应的成员函数         // corresponding member functions.         // The following function call is identical to  下面的函数调用和Set.insert(knownValues, value) 作用一样         // Set.insert(knownValues, value)         if (!knownValues.insert(value))             throw;     } } It is also possible to extend elementary types in that way: 这个也是一种扩展基本类型的(方式) library Search {     function indexOf(uint[] storage self, uint value) {         for (uint i = 0; i < self.length; i++)             if (self[i] == value) return i;         return uint(-1);     }} contract C {     using Search for uint[];     uint[] data;     function append(uint value) {         data.push(value);     }     function replace(uint _old, uint _new) {         // This performs the library function call   这样完成了库函数的调用         uint index = data.find(_old);         if (index == -1)             data.push(_new);         else             data[index] = _new;     }}
注意:所有的库函数调用都是调用实际的EVM。这意味着,如果你要使用内存或值类型,就必须执行一次拷贝操作,即使是self变量。拷贝没有完成的情况可能是存储引用变量已被使用。
Next   Previous
© Copyright 2015, Ethereum. Revision 37381072.
Built with  Sphinx  using a  theme  provided by  Read the Docs . 如果你希望 高效的 学习以太坊DApp开发,可以访问汇智网提供的 最热门 在线互动教程: 适合区块链新手的以太坊DApp实战入门教程 区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问 这个以太坊博客 。
区块链
2018-04-30 21:27:01
「深度学习福利」大神带你进阶工程师,立即查看>>>
表达式和控制结构
控制结构
除了 switch和goto,solidity的绝大多数控制结构均来自于C / JavaScript,if, else, while, for, break, continue, return, ? :, 的语义均和C / JavaScript一样。
条件语句中的括号不能省略,但在单条语句前后的花括号可以省略。
注意,(Solidity中 )没有象C和JavaScrip那样 ,从非布尔类型类型到布尔类型的转换,  所以if (1){…}不是合法的Solidity(语句)。
函数调用
内部函数调用
当前合约和函数可以直接调用(“内部”),也可以递归, 所以你会看到在这个“荒谬”的例子: contract c {   function g(uint a) returns (uint ret) { return f(); }   function f() returns (uint ret) { return g(7) + f(); }}
这些函数调用在EMV里翻译成简单的jumps 语句。当前内存没有被清除,  即通过内存引用的函数是非常高效的。仅仅同一个合约的函数可在内部被调用。
外部函数调用
表达式this.g(8);也是一个合法的函数调用,  但是这一次,这个函数将被称为“外部”的, 通过消息调用,而不是直接通过jumps调用。其他合约的函数也是外部调用。对于外部调用,所有函数参数必须被复制到内存中。
当调用其他合约的函数时, 这个调用的wei的数量被发送和gas被定义: contract InfoFeed {   function info() returns (uint ret) { return 42; } } contract Consumer {   InfoFeed feed;   function setFeed(address addr) { feed = InfoFeed(addr); }   function callFeed() { feed.info.value(10).gas(800)(); } }
注意:表达式InfoFeed(addr)执行显式的类型转换, 意思是“我们知道给定的地址的合约类型是InfoFeed”, 这并不执行构造函数。 我们也可以直接使用函数setFeed(InfoFeed _feed) { feed = _feed; }。 注意: feed.info.value(10).gas(800)是(本地)设置值和函数调用发送的gas数量, 只有最后的括号结束后才完成实际的调用。
具名调用和匿名函数参数
有参数的函数调用可以有名字,不使用参数的名字(特别是返回参数)可以省略。 contract c {   function f(uint key, uint value) { ... }   function g() {     // named arguments   具名参数     f({value: 2, key: 3});   }   // omitted parameters 省略名字的参数   function func(uint k, uint) returns(uint) {     return k;   } }
表达式计算的次序
表达式的计算顺序是不确定的(准确地说是, 顺序表达式树中的子节点表达式计算顺序是不确定的的, 但他们对节点本身,计算表达式顺序当然是确定的)。只保证语句执行顺序,以及布尔表达式的短路规则。
赋值
析构赋值并返回多个值
Solidity内部允许元组类型,即一系列的不同类型的对象的大小在编译时是一个常量。这些元组可以用来同时返回多个值,并且同时将它们分配给多个变量(或左值运算) contract C {   uint[] data;   function f() returns (uint, bool, uint) {     return (7, true, 2);   }   function g() {     // Declares and assigns the variables. Specifying the type explicitly is not possible.     var (x, b, y) = f();     // Assigns to a pre-existing variable.     (x, y) = (2, 7);     // Common trick to swap values -- does not work for non-value storage types.     (x, y) = (y, x);     // Components can be left out (also for variable declarations).     // If the tuple ends in an empty component,     // the rest of the values are discarded.     (data.length,) = f(); // Sets the length to 7     // The same can be done on the left side.     (,data[3]) = f(); // Sets data[3] to 2     // Components can only be left out at the left-hand-side of assignments, with     // one exception:     (x,) = (1,);     // (1,) is the only way to specify a 1-component tuple, because (1) is     // equivalent to 1.   } } contract C {   uint[] data;   function f() returns (uint, bool, uint) {     return (7, true, 2);   }   function g() {     // Declares and assigns the variables. Specifying the type explicitly is not possible. 声明和赋值变量,不必显示定义类型     var (x, b, y) = f();     // Assigns to a pre-existing variable. 赋值给已经存在的变量     (x, y) = (2, 7);     // Common trick to swap values -- does not work for non-value storage types. 交换值的技巧-对非值存储类型不起作用     (x, y) = (y, x);     // Components can be left out (also for variable declarations). 元素可排除(对变量声明也适用)     // If the tuple ends in an empty component, 如果元组是以空元素为结尾     // the rest of the values are discarded.  值的其余部分被丢弃     (data.length,) = f(); // Sets the length to 7 设定长度为7     // The same can be done on the left side. 同样可以在左侧做     (,data[3]) = f(); // Sets data[3] to 2  将data[3] 设为2     // Components can only be left out at the left-hand-side of assignments, with     // one exception:    组件只能在赋值的左边被排除,有一个例外     (x,) = (1,);     // (1,) is the only way to specify a 1-component tuple, because (1) is     (1,)是定义一个元素的元组,(1)是等于1     // equivalent to 1.   }}
数组和结构体的组合
对于象数组和结构体这样的非值类型,赋值的语义更复杂些。赋值到一个状态变量总是需要创建一个独立的副本。另一方面,对基本类型来说,赋值到一个局部变量需要创建一个独立的副本, 即32字节的静态类型。如果结构体或数组(包括字节和字符串)从状态变量被赋值到一个局部变量,  局部变量则保存原始状态变量的引用。第二次赋值到局部变量不修改状态,只改变引用。赋值到局部变量的成员(或元素)将改变状态。
异常
有一些自动抛出异常的情况(见下文)。您可以使用throw 指令手动抛出一个异常。异常的影响是当前执行的调用被停止和恢复(即所有状态和余额的变化均没有发生)。另外, 异常也可以通过Solidity 函数 “冒出来”, (一旦“异常”发生, 就send "exceptions", call和callcode底层函数就返回false)。
捕获异常是不可能的。
在接下来的例子中,我们将演示如何轻松恢复一个Ether转移,以及如何检查send的返回值: contract Sharer {     function sendHalf(address addr) returns (uint balance) {         if (!addr.send(msg.value/2))             throw; // also reverts the transfer to Sharer         return this.balance;     } } contract Sharer {     function sendHalf(address addr) returns (uint balance) {         if (!addr.send(msg.value/2))             throw; // also reverts the transfer to Sharer  也恢复Sharer的转移         return this.balance;     } }
目前,Solidity异常自动发生,有三种情况, : 如果你访问数组超出其长度 (即x[i] where i >= x.length) 如果一个通过消息调用的函数没有正确的执行结束(即gas用完,或本身抛出异常)。 如果一个库里不存在的函数被调用,或Ether被发送到一个函数库里。
在内部,当抛出异常时 ,Solidity就执行“非法jump”, 从而导致EMV(Ether虚拟机)恢复所有更改状态。这个原因是没有安全的方法可以继续执行,   预期的结果没有发生。由于我们想保留事务的原子性,(所以)最安全的做法是恢复所有更改,并使整个事务(或者至少调用)没有受影响。
© Copyright 2015, Ethereum. Revision 37381072.
Built with  Sphinx  using a  theme  provided by  Read the Docs .
 Read the Docsv: latest  如果你希望 高效的 学习以太坊DApp开发,可以访问汇智网提供的 最热门 在线互动教程: 适合区块链新手的以太坊DApp实战入门教程 区块链+IPFS+Node.js+MongoDB+Express去中心化以太坊电商应用开发实战
其他更多内容也可以访问 这个以太坊博客 。
区块链
2018-04-30 21:23:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
以太坊-rpc原理及实现
JSON-RPC是区块链外部调用的标配了。以太坊同样也实现了这个功能。底层支持四种协议:InProc,IPC,HTTP,WEBSOCKED。上层除了常规的方法调用之外还实现了Pub/Sub功能。本文主要分析以太坊是如何支持这些个功能的。
api发布
api接口分布在各个模块,主要分为两种 1:直接code再Node中的几个service(admin,web3j,debug etc) 2: 实现了Service接口的服务结构,已经注册的服务会调用APIs()方法获得其中的api。 //file go-ethereum/node/node.go func (n *Node) startRPC(services map[reflect.Type]Service) error { apis := n.apis() for _, service := range services { apis = append(apis, service.APIs()...) } }
node中写死的接口 // node中写死的接口 func (n *Node) apis() []rpc.API { return []rpc.API{ { Namespace: "admin", Version: "1.0", Service: NewPrivateAdminAPI(n), }, { Namespace: "admin", Version: "1.0", Service: NewPublicAdminAPI(n), Public: true, }, { Namespace: "debug", Version: "1.0", Service: debug.Handler, }, { Namespace: "debug", Version: "1.0", Service: NewPublicDebugAPI(n), Public: true, }, { Namespace: "web3", Version: "1.0", Service: NewPublicWeb3API(n), Public: true, }, } }
Ethereum 服务实现的APIs()接口 类似的还有其他的服务(dashboard,ethstats) //Ethereum 服务实现的APIs()接口 func (s *Ethereum) APIs() []rpc.API { apis := ethapi.GetAPIs(s.ApiBackend) // Append any APIs exposed explicitly by the consensus engine apis = append(apis, s.engine.APIs(s.BlockChain())...) // Append all the local APIs and return return append(apis, []rpc.API{ { Namespace: "eth", Version: "1.0", Service: NewPublicEthereumAPI(s), Public: true, }, { Namespace: "eth", Version: "1.0", Service: NewPublicMinerAPI(s), Public: true, }, { Namespace: "eth", Version: "1.0", Service: downloader.NewPublicDownloaderAPI(s.protocolManager.downloader, s.eventMux), Public: true, }, { Namespace: "miner", Version: "1.0", Service: NewPrivateMinerAPI(s), Public: false, }, { Namespace: "eth", Version: "1.0", Service: filters.NewPublicFilterAPI(s.ApiBackend, false), Public: true, }, { Namespace: "admin", Version: "1.0", Service: NewPrivateAdminAPI(s), }, { Namespace: "debug", Version: "1.0", Service: NewPublicDebugAPI(s), Public: true, }, { Namespace: "debug", Version: "1.0", Service: NewPrivateDebugAPI(s.chainConfig, s), }, { Namespace: "net", Version: "1.0", Service: s.netRPCService, Public: true, }, }...) }
这里的Service只是类型,还要注册到Server里面,原理就是反射出结构体里的类型,解析出函数方法名称(转小写),参数名称,返回类型等信息,最终每个合格的方法都会生成service实例 type service struct { name string // name for service typ reflect.Type // receiver type callbacks callbacks // registered handlers subscriptions subscriptions // available subscriptions/notifications } //反射除Service Api的结构方法 //file go-ethereum/rpc/utils.go func suitableCallbacks(rcvr reflect.Value, typ reflect.Type) (callbacks, subscriptions) { callbacks := make(callbacks) subscriptions := make(subscriptions) METHODS: for m := 0; m < typ.NumMethod(); m++ { method := typ.Method(m) mtype := method.Type //转小写 mname := formatName(method.Name) if method.PkgPath != "" { // method must be exported continue } var h callback //订阅事件类型判断 主要根据签名的入参第二位和返回参数第一位 h.isSubscribe = isPubSub(mtype) h.rcvr = rcvr h.method = method h.errPos = -1 firstArg := 1 numIn := mtype.NumIn() if numIn >= 2 && mtype.In(1) == contextType { h.hasCtx = true firstArg = 2 } if h.isSubscribe { //订阅类型 h.argTypes = make([]reflect.Type, numIn-firstArg) // skip rcvr type for i := firstArg; i < numIn; i++ { argType := mtype.In(i) if isExportedOrBuiltinType(argType) { h.argTypes[i-firstArg] = argType } else { continue METHODS } } subscriptions[mname] = &h continue METHODS } // determine method arguments, ignore first arg since it's the receiver type // Arguments must be exported or builtin types h.argTypes = make([]reflect.Type, numIn-firstArg) for i := firstArg; i < numIn; i++ { argType := mtype.In(i) if !isExportedOrBuiltinType(argType) { continue METHODS } h.argTypes[i-firstArg] = argType } // check that all returned values are exported or builtin types for i := 0; i < mtype.NumOut(); i++ { if !isExportedOrBuiltinType(mtype.Out(i)) { continue METHODS } } // when a method returns an error it must be the last returned value h.errPos = -1 for i := 0; i < mtype.NumOut(); i++ { if isErrorType(mtype.Out(i)) { h.errPos = i break } } if h.errPos >= 0 && h.errPos != mtype.NumOut()-1 { continue METHODS } switch mtype.NumOut() { case 0, 1, 2: if mtype.NumOut() == 2 && h.errPos == -1 { // method must one return value and 1 error continue METHODS } callbacks[mname] = &h } } return callbacks, subscriptions }
底层协议
底层支持了InProc,IPC,HTTP,WEBSOCKED 四种传输协议 1 InProc 直接生成RPCService实例,挂在Node上面可以直接调用。 2 IPC 监听管道,收到消息后解析成ServerCodec对象,扔给Server的ServeCodec方法使用 //file ipc.go func (srv *Server) ServeListener(l net.Listener) error { for { conn, err := l.Accept() if netutil.IsTemporaryError(err) { log.Warn("RPC accept error", "err", err) continue } else if err != nil { return err } log.Trace("Accepted connection", "addr", conn.RemoteAddr()) go srv.ServeCodec(NewJSONCodec(conn), OptionMethodInvocation|OptionSubscriptions) } } 3 HTTP 生成两个中间件,第二个中间件接收消息生成ServerCOdec,扔给Server的ServeSingleRequest方法 //file http.go func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Permit dumb empty requests for remote health-checks (AWS) if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" { return } if code, err := validateRequest(r); err != nil { http.Error(w, err.Error(), code) return } // All checks passed, create a codec that reads direct from the request body // untilEOF and writes the response to w and order the server to process a // single request. ctx := context.Background() ctx = context.WithValue(ctx, "remote", r.RemoteAddr) ctx = context.WithValue(ctx, "scheme", r.Proto) ctx = context.WithValue(ctx, "local", r.Host) body := io.LimitReader(r.Body, maxRequestContentLength) codec := NewJSONCodec(&httpReadWriteNopCloser{body, w}) defer codec.Close() w.Header().Set("content-type", contentType) srv.ServeSingleRequest(codec, OptionMethodInvocation, ctx) } 1 WEBSOCKED 与Http类型生成WebsocketHandler中间件,到消息后解析成ServerCodec对象,扔给Server的ServeCodec方法使用 //websocked.go func (srv *Server) WebsocketHandler(allowedOrigins []string) http.Handler { return websocket.Server{ Handshake: wsHandshakeValidator(allowedOrigins), Handler: func(conn *websocket.Conn) { // Create a custom encode/decode pair to enforce payload size and number encoding conn.MaxPayloadBytes = maxRequestContentLength encoder := func(v interface{}) error { return websocketJSONCodec.Send(conn, v) } decoder := func(v interface{}) error { return websocketJSONCodec.Receive(conn, v) } srv.ServeCodec(NewCodec(conn, encoder, decoder), OptionMethodInvocation|OptionSubscriptions) }, } }
rpc响应
上面四种协议再拿到ServerCodec对象后,会把这个对象传递到service的响应请数里面去。最终都是调到handle函数里面,handle里面再根据不同的类型进行响应。 func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverRequest) (interface{}, func()) { if req.err != nil { return codec.CreateErrorResponse(&req.id, req.err), nil } if req.isUnsubscribe { //取消订阅功能 if len(req.args) >= 1 && req.args[0].Kind() == reflect.String { notifier, supported := NotifierFromContext(ctx) //获取notifier对象 if !supported { // interface doesn't support subscriptions (e.g. http) return codec.CreateErrorResponse(&req.id, &callbackError{ErrNotificationsUnsupported.Error()}), nil } //取消订阅 subid := ID(req.args[0].String()) if err := notifier.unsubscribe(subid); err != nil { return codec.CreateErrorResponse(&req.id, &callbackError{err.Error()}), nil } return codec.CreateResponse(req.id, true), nil } return codec.CreateErrorResponse(&req.id, &invalidParamsError{"Expected subscription id as first argument"}), nil } if req.callb.isSubscribe { //订阅功能 subid, err := s.createSubscription(ctx, codec, req) if err != nil { return codec.CreateErrorResponse(&req.id, &callbackError{err.Error()}), nil } // active the subscription after the sub id was successfully sent to the client activateSub := func() { notifier, _ := NotifierFromContext(ctx) //获取notifier对象 notifier.activate(subid, req.svcname) //订阅事件 } return codec.CreateResponse(req.id, subid), activateSub } // regular RPC call, prepare arguments //参数生成 if len(req.args) != len(req.callb.argTypes) { rpcErr := &invalidParamsError{fmt.Sprintf("%s%s%s expects %d parameters, got %d", req.svcname, serviceMethodSeparator, req.callb.method.Name, len(req.callb.argTypes), len(req.args))} return codec.CreateErrorResponse(&req.id, rpcErr), nil } arguments := []reflect.Value{req.callb.rcvr} if req.callb.hasCtx { arguments = append(arguments, reflect.ValueOf(ctx)) } if len(req.args) > 0 { arguments = append(arguments, req.args...) } // execute RPC method and return result //执行对应的函数 reply := req.callb.method.Func.Call(arguments) if len(reply) == 0 { return codec.CreateResponse(req.id, nil), nil } //校验结果 if req.callb.errPos >= 0 { // test if method returned an error if !reply[req.callb.errPos].IsNil() { e := reply[req.callb.errPos].Interface().(error) res := codec.CreateErrorResponse(&req.id, &callbackError{e.Error()}) return res, nil } } return codec.CreateResponse(req.id, reply[0].Interface()), nil }
Pub/sub 实现
底层在context绑定一个notifier对象 if options&OptionSubscriptions == OptionSubscriptions { ctx = context.WithValue(ctx, notifierKey{}, newNotifier(codec)) }
sub/unsub的时候会通过context.Value中拿notifier对象,调用上面的方法注册或者取消注册 func NotifierFromContext(ctx context.Context) (*Notifier, bool) { n, ok := ctx.Value(notifierKey{}).(*Notifier) return n, ok }
注册 func (n *Notifier) activate(id ID, namespace string) { n.subMu.Lock() defer n.subMu.Unlock() if sub, found := n.inactive[id]; found { sub.namespace = namespace n.active[id] = sub delete(n.inactive, id) } }
注销 func (n *Notifier) unsubscribe(id ID) error { n.subMu.Lock() defer n.subMu.Unlock() if s, found := n.active[id]; found { close(s.err) delete(n.active, id) return nil } return ErrSubscriptionNotFound }
消息事件触发 func (api *PrivateAdminAPI) PeerEvents(ctx context.Context) (*rpc.Subscription, error) { // Make sure the server is running, fail otherwise server := api.node.Server() if server == nil { return nil, ErrNodeStopped } // Create the subscription //获取notifier对象 notifier, supported := rpc.NotifierFromContext(ctx) if !supported { return nil, rpc.ErrNotificationsUnsupported } //生成标识 rpcSub := notifier.CreateSubscription() go func() { events := make(chan *p2p.PeerEvent) sub := server.SubscribeEvents(events) defer sub.Unsubscribe() for { select { case event := <-events: //触发事件,发送通知消息 notifier.Notify(rpcSub.ID, event) case <-sub.Err(): return case <-rpcSub.Err(): return case <-notifier.Closed(): return } } }() return rpcSub, nil }
rpc client
对应实现的有一个rpcclient,提供了Rpc调用,事件订阅等功能 https://github.com/ethereum/go-ethereum/tree/master/rpc/client.go
参考
json: http://json.org/
json-rpc : http://www.jsonrpc.org/specification
source code : https://github.com/ethereum/go-ethereum/tree/master/rpc
区块链
2018-04-29 16:45:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
通过node.js和ipfs-api我们可以访问IPFS(星际文件系统)。
二、ipfs-api安装与使用
星际文件系统Ipfs节点提供和REST API接口,可供我们在程序代码中操作节点进行文件的上传等操作。不过大多数情况下,我们并不需要直接操作这个REST开发接口,而是使用经过封装的更友好的ipfs-api,一个nodejs包。
2.1安装nodejs
到官网下载nodejs安装包: 32位 , 64位 。下载后双击进行安装即可。
开一个控制台窗口,测试: C:
区块链
2018-04-29 16:29:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
大多数人并不需要自己重新创建一套区块链,而是基于现有的区块链底层平台去开发自己的应用,对于类似加密算法、 P2P技术、共识算法等只需要有个基本了解就可以了,暂时不需要深入研究。在文本中,将介绍三种主流的区块链底层平台:比特币、以太坊和超级账本Fabric,以及这些平台上的应用开发语言。
比特币区块链开发
最早的区块链开发便是基于比特币的区块链网络进行开发了,由于比特币是全球最广泛使用和真正意义的去中心化应用,因此,围绕比特币的各种区块链技术非常多,这里不一一描述,只针对入门开发进行介绍。
基于比特币的区块链开发有两种方式,一种相对比较简单,基于Blockchain.info的API进行开发。Blockchain.info是比特币的最流行的比特币钱包和区块查询网站,同时也提供的比特币及其区块相关API。 Blockchain.info 提供了多种主流语言的API库,包括了比特币的钱包、支付、区块、交易数据、市场数据等多方面的API。
​安装和使用Blockchain.info的API比较简单,首先你的机器需要有NodeJS环境,在Blockchain.info的申请接口授权码,安装Blockchain Wallet API服务程序,就可以开始配置和测试Blockchain Wallet API服务程序了,要进一步开发,可以根据你的开发语言选择安装API的客户端支持库。
进一步的探索,可以采用Docker容器来快速安装和配置私有节点的比特币测试网络(bitcoin-testnet)作为开发试验环境,这样可以更深入了解、调试和使用比特币区块链网络。在Bitcoin的wiki网页上面,提供很多种语言都可以调用Bitcoin的RPC,大家选择适合自己的语言具体去试验,当然,Elwin仍然推荐你使用NodeJS。
具体流程是下载比特币测试网络的Docker镜像,运行Docker镜像并启动比特币测试网络,初始化和测试区块链数据,当然为了测试少不了要先挖矿储备一下。然后安装相关开发语言的RPC支持库后,就可以调试你的区块链程序了。
以太坊区块链开发
可以说除了比特币外,以太坊目前在区块链平台是最吸引眼球的。 以太坊是一个图灵完备的区块链一站式开发平台,采用多种编程语言实现协议,采用Go语言写的客户端作为默认客户端(即与以太坊网络交互的方法, 支持其他多种语言的客户端)。
基于以太坊平台之上的应用是智能合约,这是以太坊的核心。每个智能合约有一个唯一的地址,当用户向合约的地址里发送一笔交易后(这个时候就要消耗燃料费用,也就是手续费用),该合约就被激活,然后根据交易中的额外信息,合约会运行自身的代码,最后返回一个结果。以太坊社区把基于智能合约的应用称为去中心化的应用程序(Decentralized App),相对于冷冰冰的智能合约代码,DApp拥有一个友好的界面和外加一些额外的东西,配合上图灵完备的语言,可以让用户基于合约搭建各种千变万化的DApp应用,实际上,在以太坊APP展区,已经有大大小小280个的DApp应用在展示(虽然只有一部分应用在真正运行)。
要写以太坊的智能合约有好几种语言可选,有类Javascript的Solidity,Python接近的Serpent,还有类Lisp的LLL,目前比较主流的是Solidity,推荐大家使用。当Solidity合约编译好并且发送到网络上之后,你可以通过以太坊的Mist客户端对智能合约进行测试和使用,也可以使用以太坊的web3.js JavaScript API来调用它,构建能与之交互的web应用。
由于以太坊的知名度,所以很多社区开发出更加便捷的DApp开发框架和工具,包括Truffle、Embark、Meteor、BlockApps.net APIs,使得你可以快速开发你的Dapp。你既可以搭建基于自己的以太坊私链,也可以和合作伙伴一起搭建联盟链,又或者直接将应用部署在以太坊的公共网络。 如果你希望马上开始学习以太坊DApp开发,可以访问汇智网提供的出色的在线互动教程: 以太坊DApp实战开发入门 去中心化电商DApp实战开发
超级账本Farbrc区块链开发
Fabric源于IBM,初衷为了服务于工业生产,IBM将其44,000行代码开源,是了不起的贡献,让我们可以有机会如此近的去探究区别于比特币的区块链的原理。
要基于HyperLedger进行区块链开发比想像中简单,有两种途径,一种是基于超能云(IBM中国研究院开发的超能云平台提供了各种云服务),它给区块链爱好者、开发者的区块链开发测试环境,通过超能云平台,用户能够免费、超快速创建基于Hyperledger Fabric的多节点区块链、并在自己的链上调试智能合约。Hyperledger Fabric的合约是基于Go语言的,上手比较简单。
另一种进行Fabric是自己搭建Fabric的区块链网络。安装和运行Hyperledge fabric的运行有几种方式,比较推荐是下载Fabric区块链网络的Docker镜像,运行Docker镜像并启动Fabric区块链网络,但相对于比特币和以太坊,Fabric网络的架构和安装相对复杂,除了区块链服务外,还需要另外安装运行validating peer和Certificate Authority (CA) 服务。搞定后要真正使用,还需要先用户注册和登记授权,然后才可以通过CLI 或REST API进行调试和使用。其中里面智能合约的编写,跟在超能云的区块链云服务的是一样的。此外,除了CLI或REST API,IBM还提供了gRPC API和 SDK的方式进行应用的开发。
区块链
2018-05-02 08:52:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
启动容器来执行geth命令 root @ubu-blockchain2:~# docker run -i blockchain101/ethereum-geth: 1.6 .5 geth attach http:// 45.32 .252 .88 : 8201 Welcome to the Geth JavaScript console! instance : Geth /01/v1.6.5-stable/linux-amd64/go1.8 coinbase: 0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a at block: 6064 ( Wed , 02 Aug 2017 01:13:50 UTC ) datadir: /ethcluster/779977/data/01 modules: admin:1.0 eth:1.0 net:1.0 rpc:1.0 web3:1.0
查看我们的账户和余额 eth .getBalance (eth .accounts [ 0 ]) 11000000000000000000 eth .getBalance (eth .accounts [ 1 ]) 0 eth .accounts [ 0 ] "0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a" > eth .accounts [ 1 ] "0xe82e2f0a5abd8774767b9751659976f9c4f59181"
发起一笔交易 eth .sendTransaction ({from: eth.accounts[ 0 ],to:eth.accounts[ 1 ],value:web3. toWei( 3 , 'ether' ) }) "0 x0075da712d26aea17d6647035107f509e13eaf3d113c1577db14d4cc4216caec "
查看交易细节 > eth .getTransaction ("0 x0075da712d26aea17d6647035107f509e13eaf3d113c1577db14d4cc4216caec ") { blockHash: "0x3115703894dc6015c96ef4de3e5615f416498ca1f985902b38cd70e27dab8871" , blockNumber: 1250 , from: "0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a" , gas: 90000 , gasPrice: 18000000000 , hash: "0x0075da712d26aea17d6647035107f509e13eaf3d113c1577db14d4cc4216caec" , input: "0x" , nonce: 0 , r: "0x2aef2c1fa03a0fa4172d21e3383d8c0431d45ec855b9d16efdd5eb2de90c414c" , s: "0xc4d530fb7902bf509fe56bfbea4861bf6cc16791afc9c9103c1a18f77407d1f" , to: "0xe82e2f0a5abd8774767b9751659976f9c4f59181" , transactionIndex: 0 , v: "0x17cdb6" , value: 3000000000000000000 } > eth .getBalance ( eth .accounts [1] ) 3000000000000000000
验证用户0的余额 > eth .getBalance (eth .accounts [ 0 ]) 7999622000000000000
编写一个简单的合约 contract Sample { uint public value ; function Sample( uint v){ value =v; } function set ( uint v){ value =v; } function get () constant returns ( uint ){ return value ; } }
在 remix 网页编译得到ABI接口和合约的二进制代码、 abi=[{ "constant" : true , "inputs" :[], "name" : "value" , "outputs" :[{ "name" : "" , "type" : "uint256" }], "payable" : false , "type" : "function" },{ "constant" : false , "inputs" :[{ "name" : "v" , "type" : "uint256" }], "name" : "set" , "outputs" :[], "payable" : false , "type" : "function" },{ "constant" : true , "inputs" :[], "name" : "get" , "outputs" :[{ "name" : "" , "type" : "uint256" }], "payable" : false , "type" : "function" },{ "inputs" :[{ "name" : "v" , "type" : "uint256" }], "payable" : false , "type" : "constructor" }] [{ constant: true , inputs: [], name: "value" , outputs: [{ name: "" , type: "uint256" }], payable: false , type: "function" }, { constant: false , inputs: [{ name: "v" , type: "uint256" }], name: "set" , outputs: [], payable: false , type: "function" }, { constant: true , inputs: [], name: "get" , outputs: [{ name: "" , type: "uint256" }], payable: false , type: "function" }, { inputs: [{ name: "v" , type: "uint256" }], payable: false , type: "constructor" }]
需要使用eth.contract来定义一个合约类 > sample=eth.contract(abi) { abi: [{ constant: true , inputs: [], name: "value" , outputs: [{...}], payable: false , type : "function" }, { constant: false , inputs: [{...}], name: "set" , outputs: [], payable: false , type : "function" }, { constant: true , inputs: [], name: "get" , outputs: [{...}], payable: false , type : "function" }, { inputs: [{...}], payable: false , type : "constructor" }], eth: { accounts: [ "0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a" , "0xe82e2f0a5abd8774767b9751659976f9c4f59181" ], blockNumber: 6225 , coinbase: "0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a" , compile: { lll: function () , serpent: function () , solidity: function () }, defaultAccount: undefined, defaultBlock: "latest" , gasPrice: 18000000000 , hashrate: 0 , mining: false , pendingTransactions: [], protocolVersion: "0x3f" , syncing: false , call: function () , contract: function (abi) , estimateGas: function () , filter: function (fil, callback) , getAccounts: function (callback) , getBalance: function () , getBlock: function () , getBlockNumber: function (callback) , getBlockTransactionCount: function () , getBlockUncleCount: function () , getCode: function () , getCoinbase: function (callback) , getCompilers: function () , getGasPrice: function (callback) , getHashrate: function (callback) , getMining: function (callback) , getPendingTransactions: function (callback) , getProtocolVersion: function (callback) , getRawTransaction: function () , getRawTransactionFromBlock: function () , getStorageAt: function () , getSyncing: function (callback) , getTransaction: function () , getTransactionCount: function () , getTransactionFromBlock: function () , getTransactionReceipt: function () , getUncle: function () , getWork: function () , iban: function (iban) , icapNamereg: function () , isSyncing: function (callback) , namereg: function () , resend: function () , sendIBANTransaction: function () , sendRawTransaction: function () , sendTransaction: function () , sign: function () , signTransaction: function () , submitTransaction: function () , submitWork: function () }, at: function (address, callback) , getData: function () , new: function () }
合约的二进制代码赋值给SampleHEX方便使用 SampleHEX= "0x6060604052341561000c57fe5b60405160208061013a833981016040528080519060200190919050505b806000819055505b505b60f9806100416000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633fa4f24514604e57806360fe47b11460715780636d4ce63c14608e575bfe5b3415605557fe5b605b60b1565b6040518082815260200191505060405180910390f35b3415607857fe5b608c600480803590602001909190505060b7565b005b3415609557fe5b609b60c2565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b600060005490505b905600a165627a7a72305820208c8101070c8ba5a9b32db2bf4b8062a9ba50bc2869c39ac2297938756540e80029"
把合约代码部署上链 > thesample=sample.new( 1 ,{from:eth.accounts[ 0 ],data:SampleHEX,gas: 3000000 }) { abi: [{ constant: true, inputs: [], name: "value" , outputs: [{ ... }], payable: false, type: "function" }, { constant: false, inputs: [{ ... }], name: "set" , outputs: [], payable: false, type: "function" }, { constant: true, inputs: [], name: "get" , outputs: [{ ... }], payable: false, type: "function" }, { inputs: [{ ... }], payable: false, type: "constructor" }], address: undefined, transactionHash: "0xee74bcb4461c9712ec9aca96a5a3a4c3c64be1213854d519fc8e5432b554f7a1" }
查看交易细节 > samplerecpt=eth.getTransactionReceipt( "0xee74bcb4461c9712ec9aca96a5a3a4c3c64be1213854d519fc8e5432b554f7a1" ) { blockHash: "0xddba16545af882835fb9a69a0e5f3b9287c61664837d5ea0068b38575cb665c5" , blockNumber: 6246 , contractAddress: "0x7504fa9d64ab290844b82660d43b310f8fba0276" , cumulativeGasUsed: 141836 , from : "0x4c57e7e9c2f728046ddc6e96052056a241bdbd0a" , gasUsed: 141836 , logs: [], logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" , root: "0xd1093ecaca9cc0d10e82a533a15feccedf7ff5c79fb3ebd9366ec0b35dbef478" , to : null , transactionHash: "0xee74bcb4461c9712ec9aca96a5a3a4c3c64be1213854d519fc8e5432b554f7a1" , transactionIndex: 0 }
合约命名 > samplecontract=sample.at( "0x7504fa9d64ab290844b82660d43b310f8fba0276" ) { abi: [{ constant: true, inputs: [], name: "value" , outputs: [{ ... }], payable: false, type: "function" }, { constant: false, inputs: [{ ... }], name: "set" , outputs: [], payable: false, type: "function" }, { constant: true, inputs: [], name: "get" , outputs: [{ ... }], payable: false, type: "function" }, { inputs: [{ ... }], payable: false, type: "constructor" }], address: "0x7504fa9d64ab290844b82660d43b310f8fba0276" , transactionHash: null, allEvents: function (), get: function (), set: function (), value: function () }

合约查看功能函数get(),然后调用set()函数,再get()查看时已经改变了 samplecontract .get .call () 1 > samplecontract .set .sendTransaction ( 9 , {from:eth .accounts [ 0 ], gas: 3000000 }) "0x822ee6fb4caceb7e844c533f7f3bc57806f7cb3676fb3066eb848cca46b2f38a" > samplecontract .get .call () 9
我们再打开一个终端,打开cluster1的peer02的控制台,直接at到上一个终端部署的智能合约地址并进行set操作 root @ubu - blockchain2: ~ /ethereum-docker/ethereum -docker/ethereum-testnet-docker/dockercomposefiles # docker run -i blockchain101/ethereum-geth:1.6.5 geth attach http://45.32.252.88:9201 Welcome to the Geth JavaScript console! > abi=[{ "constant" :true , "inputs" :[] , "name" : "value" , "outputs" : [{ "name" : "" , "type" : "uint256" }], "payable" :false , "type" : "function" },{ "constant" :false , "inputs" : [{ "name" : "v" , "type" : "uint256" }], "name" : "set" , "outputs" :[] , "payable" :false , "type" : "function" },{ "constant" :true , "inputs" :[] , "name" : "get" , "outputs" : [{ "name" : "" , "type" : "uint256" }], "payable" :false , "type" : "function" },{ "inputs" : [{ "name" : "v" , "type" : "uint256" }], "payable" :false , "type" : "constructor" }] > sample=eth.contract(abi) SampleHEX = "0x6060604052341561000c57fe5b60405160208061013a833981016040528080519060200190919050505b806000819055505b505b60f9806100416000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633fa4f24514604e57806360fe47b11460715780636d4ce63c14608e575bfe5b3415605557fe5b605b60b1565b6040518082815260200191505060405180910390f35b3415607857fe5b608c600480803590602001909190505060b7565b005b3415609557fe5b609b60c2565b6040518082815260200191505060405180910390f35b60005481565b806000819055505b50565b600060005490505b905600a165627a7a72305820208c8101070c8ba5a9b32db2bf4b8062a9ba50bc2869c39ac2297938756540e80029"
直接把合约地址赋值并进行set操作 samplecontract=sample .at ( "0x7504fa9d64ab290844b82660d43b310f8fba0276" ) > samplecontract .get .call () 9
简单的方法来了。智能合约的部署需要编译,这里用在线编译:
https://ethereum.github.io/browser-solidity/#version=soljson-v0.4.14+commit.c2215d46.js
修改编译好的abi和对象名称: 这里在网上找了个代币的只能合约,可以进行充值、转账和查询,issue 函数可以向充值以太到合约账户,transfer 函数可以向其他账号发送token,getBalance 函数可以获取某个账号的token余额,代码如下: pragma solidity ^ 0.4 .2 ; contract Token { address issuer; mapping (address => uint) balances; event Issue(address account, uint amount); event Transfer(address from, address to,uint amount); function Token () { issuer = msg.sender; } function issue (address account, uintamount) { if (msg.sender != issuer) throw ; balances[account] += amount; } function transfer (address to, uint amount) { if (balances[msg.sender] < amount) throw ; balances[msg.sender] -= amount; balances[to] += amount; Transfer(msg.sender, to, amount); } function getBalance (address account) constant returns (uint) { return balances[account]; } } 修改编译好的gas和对象名称: varbrowser_untitled_sol_tokenContract =web3.eth.contract([{ "constant" : false , "inputs" :[{ "name" : "account" , "type" : "address" },{ "name" : "amount" , "type" : "uint256" }], "name" : "issue" , "outputs" :[], "payable" : false , "type" : "function" },{ "constant" : false , "inputs" :[{ "name" : "to" , "type" : "address" },{ "name" : "amount" , "type" : "uint256" }], "name" : "transfer" , "outputs" :[], "payable" : false , "type" : "function" },{ "constant" : true , "inputs" :[{ "name" : "account" , "type" : "address" }], "name" : "getBalance" , "outputs" :[{ "name" : "" , "type" : "uint256" }], "payable" : false , "type" : "function" },{ "inputs" :[], "payable" : false , "type" : "constructor" },{ "anonymous" : false , "inputs" :[{ "indexed" : false , "name" : "account" , "type" : "address" },{ "indexed" : false , "name" : "amount" , "type" : "uint256" }], "name" : "Issue" , "type" : "event" },{ "anonymous" : false , "inputs" :[{ "indexed" : false , "name" : "from" , "type" : "address" },{ "indexed" : false , "name" : "to" , "type" : "address" },{ "indexed" : false , "name" : "amount" , "type" : "uint256" }], "name" : "Transfer" , "type" : "event" }]); var token =browser_untitled_sol_tokenContract. new ( { from: web3.eth.accounts[ 0 ], data: '0x6060604052341561000f57600080fd5b5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505b5b6103d2806100616000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063867904b414610054578063a9059cbb14610096578063f8b2cb4f146100d8575b600080fd5b341561005f57600080fd5b610094600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610125565b005b34156100a157600080fd5b6100d6600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101d2565b005b34156100e357600080fd5b61010f600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061035c565b6040518082815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561018057600080fd5b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055505b5050565b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561021e57600080fd5b80600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254039250508190555080600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825401925050819055507fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef338383604051808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001828152602001935050505060405180910390a15b5050565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b9190505600a165627a7a723058204afe007a03446d43d13ac892e6dba9d032f540a11ff427d26c22560727cbea2f0029' , gas: '4300000' }, function (e, contract) { console.log(e, contract); if ( typeof contract.address !== 'undefined' ) { console.log( 'Contract mined! address:' + contract.address + ' transactionHash: ' + contract.transactionHash); } })
此时输入合约部署的实例a_demotypes, 可以看到a_demotypes的详情。
控制台调用 > a_demotypes { abi: [{ constant: false, inputs: [{ ... }], name: "f" , outputs: [{ ... }], payable: false, type: "function" }], address: "0x54ed7a5f5a63ddada3bfe83b3e632adabaa5fc2f" , transactionHash: "0x69cde62bcd6458e14f40497f4840f422911d63f5dea2b3a9833e6810db64a1c9" , allEvents: function (), f: function () }
这里重点就是address表示的是合约的地址,你会发现这个和账号的信息结果一样,其实你也可以把这个合约地址看做是一个账号地址,后面我们外部调用到的就是这个合约地址。 充值 token .issue .sendTransaction ( eth .accounts [0] ,100, {from: eth.accounts[ 0 ] }); 发送 token token .transfer ( eth .accounts [1] , 30, {from: eth.accounts[ 0 ] }) 查看余额 token .getBalance () 控制台调用就不多说,和 Java 对象调用一样,直接调用即可
外部接口与智能合约交互
以太坊对外提供的有很多接口JSON RPC接口,web3接口,这里我们用JSON RPC接口。
相关API: https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sendrawtransaction
合约交互的原理
合约的交互都是一次交易,而我们要做的就是把要调用的方法和参数按照api规定的以参数的形式向区块请求一次交易,ethereum接收到我们的请求后通过解析传递的参数来执行相关的合约代码。
RPC接口给我们提供了俩个方法: eth_sendTransaction和eth_call。 eth_sendTransaction Createsnew message call transaction or a contract creation, if the data field containscode. Parameters Object - The transaction object from : DATA, 20 Bytes - The address the transaction is send from . to : DATA, 20 Bytes - ( optional when creating new contract) The address the transaction is directed to . gas: QUANTITY - ( optional , default : 90000 ) Integer of the gas provided for the transaction execution. It will return unused gas. gasPrice: QUANTITY - ( optional , default : To -Be-Determined) Integer of the gasPrice used for each paid gas value: QUANTITY - ( optional ) Integer of the value send with this transaction data: DATA - The compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI nonce: QUANTITY - ( optional ) Integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce.
可以看到,如果我们创建的为合约时,我们只需要from,to(文档上写的是可选的,但是实际操作中没有to为null的话合约不能正常执行,建议还是加上,这个值就是前面我们部署合约后生成的合约address),data。Data的属性的值具体可以看Contract ABI。这里大概说下:
Data的值相对来说不是固定的,具体怎么生成与合约的参数类型,参数数量都有关联。这里我们以部署的token合约的三个方法为例:
充值issue (address account, uint amount)
这个方法有俩个参数,address充值账号,uint充值数量。
根据Contract ABI,data值应该为方法名的sha3的前8个字节+参数的64字节,不够前面补充为0。
这里方法名并不是issue (address account, uint amount)而是issue(address,uint256)的sha3值。 web3 .sha 3( "issue(address,uint256)" ) "0x867904b44b606800f6f10498e11292d04ea19bfc7fe4bc0f1695aa516381f73d"

我们往第一个账号充值10,这里的数据不是以太币,而是我们自己创建的代币。 eth .accounts [ 0 ] "0x0cc9684af605bae10de801218321c1336bb62946"

将10转换为16进制为 000000000000000000000000000000000000000000000000000000000000000 a

那么data的数据为: 0x867904b4000000000000000000000000fdf57e81872562a6112656f961944ce821fdf7eb000000000000000000000000000000000000000000000000000000000000000a

那么最后我们调用eth_sendTransaction方法传递参数JSON对象为: { from: 0 xfdf57e81872562a6112656f961944ce821fdf7eb, to: 0 x7fe133950fc010ce41322d88f64f1918b9abb3a3, data: 0 x867904b4000000000000000000000000fdf57e81872562a6112656f961944ce821fdf7eb000000000000000000000000000000000000000000000000000000000000000a }

返回:此交易的hash值,此时该交易还没有执行,只是创建,还需要矿工通过挖矿才能完成。
调用接口方法: JsonRpcHttpClient client = newJsonRpcHttpClient( new URL(“http: //127.0.0.1:8545”)); Object result = client.invoke(methodName, params , Object. class );

通过控制台查看第一个账号已有代币: token .getbalance (eth .accounts [ 0 ])

执行调用接口代码。返回交易hash值: 0x2013764d1c3fea680f9015353497aa5f9f8d61580a3bd0524b3613b34329c095

此时控制台输入:
交易和充值一样,需要注意的是代币转出账号为from属性的值,代币转入账号为data属性里的值,to对应的是合约地址。
eth_call Executes anew message call immediately without creating a transaction on the block chain. Parameters Object - The transaction call object from : DATA, 20 Bytes - (optional) The address the transaction is sent from . to : DATA, 20 Bytes - The address the transaction is directed to . gas: QUANTITY - (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions. gasPrice: QUANTITY - (optional) Integer of the gasPrice used for each paid gas value: QUANTITY - (optional) Integer of the value send with this transaction data: DATA - (optional) Hash of the method signature and encoded parameters. For details seeEthereum Contract ABI QUANTITY|TAG - integer block number, or the string "latest" , "earliest" or "pending" , see the default block parameter
这个方法返回一条信息给我们,相当于数据库的查询功能,参数也是三个,from,to,data,数据格式都是一样的。
查询getBalance(address account)
查询方法hash码: web3 .sha 3( "getBalance(address)" ) "0xf8b2cb4f3943230388faeee074f1503714bff212640051caba01411868d14ae3"
查询我们上一步充值的账号,那么传递的参数data为: 0xf8b2cb4f000000000000000000000000fdf57e81872562a6112656f961944ce821fdf7eb
eth_call方法最后参数为: { from: 0 xfdf57e81872562a6112656f961944ce821fdf7eb, to: 0 x7fe133950fc010ce41322d88f64f1918b9abb3a3, data: 0 xf8b2cb4f000000000000000000000000fdf57e81872562a6112656f961944ce821fdf7eb }
注意,这个方法需要俩参数,处理一个JSONobject外,还有一个字符串参数,这俩可以为“”或者”latest”, “earliest” or “pending”
调用接口返回一个16进制字符串:
0x0000000000000000000000000000000000000000000000000000000000000071就是该账号的代币数量,转换为十进制为:113,与控制查询一致。
这就是一个智能合约的交互过程。是不是很简单啊。
转自:https://blog.csdn.net/ddffr/article/details/76549320
安利两个实战教程:
1. 适合区块链新手的以太坊DApp开发
2. 用区块链、星际文件系统(IPFS)、Node.js和MongoDB来构建电商平台
区块链
2018-05-01 23:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
以太坊 EVM原理与实现
通常智能合约的开发流程是用solidlity编写逻辑代码,再通过编译器编译元数据,最后再发布到以太坊上。以太坊底层通过EVM模块支持合约的执行与调用,调用时根据合约地址获取到代码,生成环境后载入到EVM中运行。
代码结构 . ├── analysis.go //跳转目标判定 ├── common.go ├── contract.go //合约数据结构 ├── contracts.go //预编译好的合约 ├── errors.go ├── evm.go //执行器 对外提供一些外部接口 ├── gas.go //call gas花费计算 一级指令耗费gas级别 ├── gas_table.go //指令耗费计算函数表 ├── gen_structlog.go ├── instructions.go //指令操作 ├── interface.go ├── interpreter.go //解释器 调用核心 ├── intpool.go //int值池 ├── int_pool_verifier_empty.go ├── int_pool_verifier.go ├── jump_table.go //指令和指令操作(操作,花费,验证)对应表 ├── logger.go //状态日志 ├── memory.go //EVM 内存 ├── memory_table.go //EVM 内存操作表 主要衡量操作所需内存大小 ├── noop.go ├── opcodes.go //Op指令 以及一些对应关系 ├── runtime │   ├── env.go //执行环境 │   ├── fuzz.go │   └── runtime.go //运行接口 测试使用 ├── stack.go //栈 └── stack_table.go //栈验证
指令
OpCode
文件opcodes.go中定义了所有的OpCode,该值是一个byte,合约编译出来的bytecode中,一个OpCode就是上面的一位。opcodes按功能分为9组(运算相关,块操作,加密相关等)。 //算数相关 const ( // 0x0 range - arithmetic ops STOP OpCode = iota ADD MUL SUB DIV SDIV MOD SMOD ADDMOD MULMOD EXP SIGNEXTEND )
Instruction
文件jump.table.go定义了四种指令集合,每个集合实质上是个256长度的数组,名字翻译过来是(荒地,农庄,拜占庭,君士坦丁堡)估计是对应了EVM的四个发展阶段。指令集向前兼容。 frontierInstructionSet = NewFrontierInstructionSet() homesteadInstructionSet = NewHomesteadInstructionSet() byzantiumInstructionSet = NewByzantiumInstructionSet() constantinopleInstructionSet = NewConstantinopleInstructionSet()
具体每条指令结构如下,字段意思见注释。 type operation struct { //对应的操作函数 execute executionFunc // 操作对应的gas消耗 gasCost gasFunc // 栈深度验证 validateStack stackValidationFunc // 操作所需空间 memorySize memorySizeFunc halts bool // 运算中止 jumps bool // 跳转(for) writes bool // 是否写入 valid bool // 操作是否有效 reverts bool // 出错回滚 returns bool // 返回 }
按下面的ADD指令为例
定义 ADD: { execute: opAdd, gasCost: constGasFunc(GasFastestStep), validateStack: makeStackFunc(2, 1), valid: true, },
操作
不同的操作有所不同,操作对象根据指令不同可能影响栈,内存,statedb。 func opAdd(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { //弹出一个值,取出一个值(这个值依旧保存在栈上面,运算结束后这个值就改变成结果值) x, y := stack.pop(), stack.peek() //加运算 math.U256(y.Add(x, y)) //数值缓存 evm.interpreter.intPool.put(x) return nil, nil }
gas花费
不同的运算有不同的初始值和对应的运算方法,具体的方法都定义在gas_table里面。 按加法的为例,一次加操作固定耗费为3。 //固定耗费 func constGasFunc(gas uint64) gasFunc { return func(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { return gas, nil } }
除此之外还有两个定义会影响gas的计算,通常作为量化的一个单位。 //file go-ethereum/core/vm/gas.go const ( GasQuickStep uint64 = 2 GasFastestStep uint64 = 3 GasFastStep uint64 = 5 GasMidStep uint64 = 8 GasSlowStep uint64 = 10 GasExtStep uint64 = 20 GasReturn uint64 = 0 GasStop uint64 = 0 GasContractByte uint64 = 200 ) //file go-ethereum/params/gas_table.go type GasTable struct { ExtcodeSize uint64 ExtcodeCopy uint64 Balance uint64 SLoad uint64 Calls uint64 Suicide uint64 ExpByte uint64 // CreateBySuicide occurs when the // refunded account is one that does // not exist. This logic is similar // to call. May be left nil. Nil means // not charged. CreateBySuicide uint64 }
memorySize
因为加操作不需要申请内存因而memorySize为默认值0。
栈验证
先验证栈上的操作数够不够,再验证栈是否超出最大限制,加法在这里仅需验证其参数够不够,运算之后栈是要减一的。 func makeStackFunc(pop, push int) stackValidationFunc { return func(stack *Stack) error { //深度验证 if err := stack.require(pop); err != nil { return err } //最大值验证 //StackLimit uint64 = 1024 if stack.len()+push-pop > int(params.StackLimit) { return fmt.Errorf("stack limit reached %d (%d)", stack.len(), params.StackLimit) } return nil } }
智能合约
合约是EVM智能合约的存储单位也是解释器执行的基本单位,包含了代码,调用人,所有人,gas相关的信息. type Contract struct { // CallerAddress is the result of the caller which initialised this // contract. However when the "call method" is delegated this value // needs to be initialised to that of the caller's caller. CallerAddress common.Address caller ContractRef self ContractRef jumpdests destinations // result of JUMPDEST analysis. Code []byte CodeHash common.Hash CodeAddr *common.Address Input []byte Gas uint64 value *big.Int Args []byte DelegateCall bool }
EVM原生预编译了一批合约,定义在contracts.go里面。主要用于加密操作。 // PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum // contracts used in the Byzantium release. var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, common.BytesToAddress([]byte{4}): &dataCopy{}, common.BytesToAddress([]byte{5}): &bigModExp{}, common.BytesToAddress([]byte{6}): &bn256Add{}, common.BytesToAddress([]byte{7}): &bn256ScalarMul{}, common.BytesToAddress([]byte{8}): &bn256Pairing{}, }
执行机

EVM中栈用于保存操作数,每个操作数的类型是big.int,这就是网上很多人说EVM是256位虚拟机的原因。执行opcode的时候,从上往下弹出操作数,作为操作的参数。 type Stack struct { data []*big.Int } func (st *Stack) push(d *big.Int) { // NOTE push limit (1024) is checked in baseCheck //stackItem := new(big.Int).Set(d) //st.data = append(st.data, stackItem) st.data = append(st.data, d) } func (st *Stack) peek() *big.Int { return st.data[st.len()-1] } func (st *Stack) pop() (ret *big.Int) { ret = st.data[len(st.data)-1] st.data = st.data[:len(st.data)-1] return }
内存
内存用于一些内存操作(MLOAD,MSTORE,MSTORE8)及合约调用的参数拷贝(CALL,CALLCODE)。
内存数据结构,维护了一个byte数组,MLOAD,MSTORE读取存入的时候都要指定位置及长度才能准确的读写。 type Memory struct { store []byte lastGasCost uint64 } // Set sets offset + size to value func (m *Memory) Set(offset, size uint64, value []byte) { // length of store may never be less than offset + size. // The store should be resized PRIOR to setting the memory if size > uint64(len(m.store)) { panic("INVALID memory: store empty") } // It's possible the offset is greater than 0 and size equals 0. This is because // the calcMemSize (common.go) could potentially return 0 when size is zero (NO-OP) if size > 0 { copy(m.store[offset:offset+size], value) } } func (self *Memory) Get(offset, size int64) (cpy []byte) { if size == 0 { return nil } if len(self.store) > int(offset) { cpy = make([]byte, size) copy(cpy, self.store[offset:offset+size]) return } return }
内存操作 func opMload(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { offset := stack.pop() val := evm.interpreter.intPool.get().SetBytes(memory.Get(offset.Int64(), 32)) stack.push(val) evm.interpreter.intPool.put(offset) return nil, nil } func opMstore(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { // pop value of the stack mStart, val := stack.pop(), stack.pop() memory.Set(mStart.Uint64(), 32, math.PaddedBigBytes(val, 32)) evm.interpreter.intPool.put(mStart, val) return nil, nil } func opMstore8(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { off, val := stack.pop().Int64(), stack.pop().Int64() memory.store[off] = byte(val & 0xff) return nil, nil }
stateDb
合约本身不保存数据,那么合约的数据是保存在哪里呢?合约及其调用类似于数据库的日志,保存了合约定义以及对他的一系列操作,只要将这些操作执行一遍就能获取当前的结果,但是如果每次都要去执行就太慢了,因而这部分数据是会持久化到stateDb里面的。code中定义了两条指令SSTORE SLOAD用于从db中读写合约当前的状态。 func opSload(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { loc := common.BigToHash(stack.pop()) val := evm.StateDB.GetState(contract.Address(), loc).Big() stack.push(val) return nil, nil } func opSstore(pc *uint64, evm *EVM, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { loc := common.BigToHash(stack.pop()) val := stack.pop() evm.StateDB.SetState(contract.Address(), loc, common.BigToHash(val)) evm.interpreter.intPool.put(val) return nil, nil }
执行过程
执行入口定义在evm.go中,功能就是组装执行环境(代码,执行人关系,参数等)。 func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { if evm.vmConfig.NoRecursion && evm.depth > 0 { return nil, gas, nil } // 合约调用深度检查 if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } // balance 检查 if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } var ( to = AccountRef(addr) //保存当前状态,如果出错,就回滚到这个状态 snapshot = evm.StateDB.Snapshot() ) if !evm.StateDB.Exist(addr) { //创建调用对象的stateObject precompiles := PrecompiledContractsHomestead if evm.ChainConfig().IsByzantium(evm.BlockNumber) { precompiles = PrecompiledContractsByzantium } if precompiles[addr] == nil && evm.ChainConfig().IsEIP158(evm.BlockNumber) && value.Sign() == 0 { return nil, gas, nil } evm.StateDB.CreateAccount(addr) } //调用别人合约可能需要花钱 evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value) //创建合约环境 contract := NewContract(caller, to, value, gas) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) start := time.Now() // Capture the tracer start/end events in debug mode if evm.vmConfig.Debug && evm.depth == 0 { evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) defer func() { // Lazy evaluation of the parameters evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) }() } //执行操作 ret, err = run(evm, contract, input) // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in homestead this also counts for code storage gas errors. if err != nil { //错误回滚 evm.StateDB.RevertToSnapshot(snapshot) if err != errExecutionReverted { contract.UseGas(contract.Gas) } } return ret, contract.Gas, err }
类似的函数有四个。详细区别见最后的参考。 Call A->B A,B的环境独立 CallCode、 和Call类似 区别在于storage位置不一样 DelegateCall、 和CallCode类似,区别在于msg.send不一样 StaticCall 和call相似 只是不能修改状态
Contract和参数构造完成后调用执行函数,执行函数会检查调用的是否会之前编译好的原生合约,如果是原生合约则调用原生合约,否则调用解释器执行函数运算合约。 // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) { if contract.CodeAddr != nil { precompiles := PrecompiledContractsHomestead if evm.ChainConfig().IsByzantium(evm.BlockNumber) { precompiles = PrecompiledContractsByzantium } if p := precompiles[*contract.CodeAddr]; p != nil { return RunPrecompiledContract(p, input, contract) } } return evm.interpreter.Run(contract, input) }
解释器 func (in *Interpreter) Run(contract *Contract, input []byte) (ret []byte, err error) { //返回数据 in.returnData = nil var ( op OpCode // 当前指令 mem = NewMemory() // 内存 stack = newstack() // 栈 pc = uint64(0) // 指令位置 cost uint64 // gas花费 pcCopy uint64 // debug使用 gasCopy uint64 // debug使用 logged bool // debug使用 ) contract.Input = input //函数入参 //*****省略****** for atomic.LoadInt32(&in.evm.abort) == 0 { //获取一条指令及指令对应的操作 op = contract.GetOp(pc) operation := in.cfg.JumpTable[op] //valid校验 if !operation.valid { return nil, fmt.Errorf("invalid opcode 0x%x", int(op)) } //栈校验 if err := operation.validateStack(stack); err != nil { return nil, err } //修改检查 if err := in.enforceRestrictions(op, operation, stack); err != nil { return nil, err } var memorySize uint64 //计算内存 按操作所需要的操作数来算 if operation.memorySize != nil { memSize, overflow := bigUint64(operation.memorySize(stack)) if overflow { return nil, errGasUintOverflow } // if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow { return nil, errGasUintOverflow } } // 校验cost 调用前面提到的costfunc 计算本次操作cost消耗 cost, err = operation.gasCost(in.gasTable, in.evm, contract, stack, mem, memorySize) if err != nil || !contract.UseGas(cost) { return nil, ErrOutOfGas //超出挂掉 } if memorySize > 0 { //如果本次操作需要消耗memory ,扩展memory mem.Resize(memorySize) } // 执行操作 res, err := operation.execute(&pc, in.evm, contract, mem, stack) if verifyPool { verifyIntegerPool(in.intPool) } // 如果遇到return 设置返回值 if operation.returns { in.returnData = res } switch { case err != nil: return nil, err //报错 case operation.reverts: //出错回滚 return res, errExecutionReverted case operation.halts: return res, nil //停止 case !operation.jumps: //跳转 pc++ } } return nil, nil }
Solidity案例
和其他语言类似,有了字节码运行机,就可以在字节码上面再组织其他高级语言,而solidlity语言就是实现了这样的语言编译器,方便了合约编写,有利于推广以太坊dapp开发。 pragma solidity ^0.4.17; contract simple { uint num = 0; function simple(){ num = 123; } function add(uint i) public returns(uint){ uint m = 111; num =num * i+m; return num; } }
生成的Opcodes码
JUMPDEST 函数入口
PUSH + JUMPI/JUMP 类似于调用函数
CALLDATASIZE + CALLDATALOAD 大约是获取函数参数 .code PUSH 80 contract simple {\n uint ... PUSH 40 contract simple {\n uint ... MSTORE contract simple {\n uint ... PUSH 0 0 //成员变量初始值 DUP1 uint num = 0 //从下面这条指令可以看出,初始化的时候成员变量就会存到statedb里面去 SSTORE uint num = 0 CALLVALUE function simple(){\n nu... DUP1 olidity ^ ISZERO a PUSH [tag] 1 a JUMPI a PUSH 0 r DUP1 o REVERT .17;\n contra tag 1 a //下面部分是构造函数执行的部分 JUMPDEST a POP function simple(){\n nu... PUSH 7B 123 PUSH 0 num DUP2 num = 123 SWAP1 num = 123 //改变成员变量最后都会写入到statedb里面去 SSTORE num = 123 POP num = 123 PUSH #[$] 0000000000000000000000000000000000000000000000000000000000000000 contract simple {\n uint ... DUP1 contract simple {\n uint ... PUSH [$] 0000000000000000000000000000000000000000000000000000000000000000 contract simple {\n uint ... PUSH 0 contract simple {\n uint ... CODECOPY contract simple {\n uint ... PUSH 0 contract simple {\n uint ... RETURN contract simple {\n uint ... //上面部分做完初始化之后并不会进入到runtime阶段 .data 0: .code //下面这段代码大约是处理参数的 PUSH 80 contract simple {\n uint ... PUSH 40 contract simple {\n uint ... MSTORE contract simple {\n uint ... PUSH 4 contract simple {\n uint ... CALLDATASIZE contract simple {\n uint ... LT contract simple {\n uint ... PUSH [tag] 1 contract simple {\n uint ... JUMPI contract simple {\n uint ... PUSH 0 contract simple {\n uint ... CALLDATALOAD contract simple {\n uint ... PUSH 100000000000000000000000000000000000000000000000000000000 contract simple {\n uint ... SWAP1 contract simple {\n uint ... DIV contract simple {\n uint ... PUSH FFFFFFFF contract simple {\n uint ... AND contract simple {\n uint ... DUP1 contract simple {\n uint ... PUSH 1003E2D2 contract simple {\n uint ... EQ contract simple {\n uint ... PUSH [tag] 2 contract simple {\n uint ... JUMPI contract simple {\n uint ... tag 1 contract simple {\n uint ... JUMPDEST contract simple {\n uint ... PUSH 0 contract simple {\n uint ... DUP1 contract simple {\n uint ... REVERT contract simple {\n uint ... tag 2 function add(uint i) public re... JUMPDEST function add(uint i) public re... CALLVALUE function add(uint i) public re... DUP1 olidity ^ ISZERO a PUSH [tag] 3 a JUMPI a PUSH 0 r DUP1 o REVERT .17;\n contra tag 3 a JUMPDEST a POP function add(uint i) public re... PUSH [tag] 4 function add(uint i) public re... PUSH 4 function add(uint i) public re... DUP1 function add(uint i) public re... CALLDATASIZE function add(uint i) public re... SUB function add(uint i) public re... DUP2 function add(uint i) public re... ADD function add(uint i) public re... SWAP1 function add(uint i) public re... DUP1 function add(uint i) public re... DUP1 function add(uint i) public re... CALLDATALOAD function add(uint i) public re... SWAP1 function add(uint i) public re... PUSH 20 function add(uint i) public re... ADD function add(uint i) public re... SWAP1 function add(uint i) public re... SWAP3 function add(uint i) public re... SWAP2 function add(uint i) public re... SWAP1 function add(uint i) public re... POP function add(uint i) public re... POP function add(uint i) public re... POP function add(uint i) public re... PUSH [tag] 5 function add(uint i) public re... JUMP function add(uint i) public re... tag 4 function add(uint i) public re... JUMPDEST function add(uint i) public re... PUSH 40 function add(uint i) public re... MLOAD function add(uint i) public re... DUP1 function add(uint i) public re... DUP3 function add(uint i) public re... DUP2 function add(uint i) public re... MSTORE function add(uint i) public re... PUSH 20 function add(uint i) public re... ADD function add(uint i) public re... SWAP2 function add(uint i) public re... POP function add(uint i) public re... POP function add(uint i) public re... PUSH 40 function add(uint i) public re... MLOAD function add(uint i) public re... DUP1 function add(uint i) public re... SWAP2 function add(uint i) public re... SUB function add(uint i) public re... SWAP1 function add(uint i) public re... RETURN function add(uint i) public re... tag 5 function add(uint i) public re... //函数内容 JUMPDEST function add(uint i) public re... //这下面就是函数的代码了 PUSH 0 uint //局部变量在栈里面 DUP1 uint m PUSH 6F 111 SWAP1 uint m = 111 POP uint m = 111 //从push0到这里实现了定义局部变量并赋值 DUP1 m DUP4 i //获取参数 PUSH 0 num SLOAD num //上面那句和这句实现了读取成员变量 MUL num * i //乘 ADD num * i+m //加 PUSH 0 num DUP2 num =num * i+m SWAP1 num =num * i+m //这三句赋值 SSTORE num =num * i+m //成员变量存储 POP num =num * i+m //下面几句实现return PUSH 0 num SLOAD num SWAP2 return num POP return num POP function add(uint i) public re... SWAP2 function add(uint i) public re... SWAP1 function add(uint i) public re... POP function add(uint i) public re... JUMP [out] function add(uint i) public re... .data
参考
Call、CallCode、DelegateCall: https://ethereum.stackexchange.com/questions/3667/difference-between-call-callcode-and-delegatecall
solidity结构: https://solidity.readthedocs.io/en/develop/structure-of-a-contract.html#
runtime bytecode和bytecode : https://ethereum.stackexchange.com/questions/13086/solc-bin-vs-bin-runtime/13087#13087
remix: https://remix.ethereum.org/
区块链
2018-05-01 22:20:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
为什么你要学习区块链技术开发?在回答这个问题之前,需要先指出一点: 区块链现在是一个过度估值的领域, 这些高估值是不可持续的,而且肯定会崩溃。
这样的泡沫之前发生过,并且可能会再次发生。 但是如果长期在这个领域工作,你将学会摆脱泡沫的影响。 用Emin Gun Sirer的话来说, 价格是加密货币中最无趣的部分,区块链才是最重要的部分,这一技术终将改变世界 。
我无法帮你决定是否应该进入区块链开发领域,但可以告诉你当初打动我进入这一领域的 五个理由:
No.1 区块链技术现在还处于早期阶段
比特币大约是在10年前发明的,但是在最近的几年,创新才开始提速,尤其是在2015年推出了 以太坊之后。这个领域的大多数新公司以及创意都建立在以太坊之上,而以太坊目前还是非常 不成熟的。
即使你现在才开始,依然可以在几年内成为世界级的区块链专家。 大多数人只是没有这么长时间, 这不会很难赶上。你现在开始,就像那些在2000年左右开始研究深度学习的专家。
No.2 区块链领域还没有建立强大的人才渠道
大学里大多数最优秀、最聪明的学生,目前都专注于机器学习、网络编程或游戏开发。 虽然 区块链在公众话题中变得越来越热门,但它仍然是一个怪异而且富有颠覆性的话题。
早期,区块链完全是数字朋克、偏执狂和怪人的王国。 这一点只是最近才开始改变。 即使只作 一个好奇而心态开放的开发者,你也会为这个领域带来很多价值。
No.3 区块链的创新大部分都是在学术界之外发生的
据我们所知,中本聪不是一名学者, 也没有大学或研究机构在持续地研究区块链。 这一领域的 大部分创新都是由爱好者、企业家和独立研究人员推动的。 几乎所有你需要知道的东西都可以在 白皮书、博客文章、slack频道和开源软件中找到。 所需要的就是卷起你的袖子,加入这个战场!
No.4 对区块链开发人才的需求远远超过供应
在这个领域没有足够的开发人员,而且无法快速得到培训。 每个人都在竞相聘请区块链技术人才,而项目正 在感受人才紧缺的压力。许多最好的公司无法为挽留员工而支付足够的钱,因为区块链人才有太多 的机会!如果你掌握了一些区块链技术,就很容易找到工作。
No.5 加密货币真的很酷
你还可以在哪里制造出像去中心化加密货币这样的科幻级别的产品? 现在就像狂野的西部大开发时期 - 这是 一把双刃剑,同时带来了好的和坏的影响。 区块链领域需要提供更多的透明度并最终会迎来监管。 但毫无疑问,加密货币是你现在可以从事的最具创新性的领域之一。
Valal Ravikant在最近的一次采访中表示 :成功的关键是提供人们想要但不知道如何实现的东西。 所以应该构建一个没人知道如何实现的东西。目前,区块链是全新的,还有很多东西需要弄清楚。 如果你能够成功地打造去中心化技术的未来,世界必将给你巨大的回报。 如果你希望马上开始学习以太坊区块链技术开发,可以访问汇智网提供的出色的在线互动教程: 以太坊区块链应用开发入门 以太坊区块链去中心化电商应用实战开发
区块链
2018-05-01 19:37:00