在Solidity中,一个智能合约由一组代码(合约的函数)和数据(合约的状态)组成。智能合约位于以太坊区块链上的一个特殊地址。uint storedData*;* 这行代码声明了一个状态变量,变量名为storedData,类型为 uint (256bits无符号整数)。你能够认为它就像数据库里面的一个存储单元,跟管理数据库同样,能够经过调用函数查询和修改它。在以太坊中,一般只有合约的拥有者才能这样作。在这个例子中,函数 set 和 get 分别用于修改和查询变量的值。node
跟不少其余语言同样,访问状态变量时,不须要在前面增长 this. 这样的前缀。git
这个合约还没法作不少事情(受限于以太坊的基础设施),仅仅是容许任何人储存一个数字。并且世界上任何一我的均可以来存取这个数字,缺乏一个(可靠的)方式来保护你发布的数字。任何人均可以调用set方法设置一个不一样的数字覆盖你发布的数字。可是你的数字将会留存在区块链的历史上。稍后咱们会学习如何增长一个存取限制,使得只有你才能修改这个数字。程序员
先从一个很是基础的例子开始,不用担忧你如今还一点都不了解,咱们将逐步了解到更多的细节。github
contract SimpleStorage { uint storedData; function set(uint x) { storedData = x; } function get() constant returns (uint retVal) { return storedData; } }
接下来的合约将实现一个形式最简单的加密货币。空中取币再也不是一个魔术,固然只有建立合约的人才能作这件事情(想用其余货币发行模式也很简单,只是实现细节上的差别)。并且任何人均可以发送货币给其余人,不须要注册用户名和密码,只要有一对以太坊的公私钥便可。数据库
Note浏览器
对于在线solidity环境来讲,这不是一个好的例子。若是你使用在线solidity环境 来尝试这个例子。调用函数时,将没法改变from的地址。因此你只能扮演铸币者的角色,能够铸造货币并发送给其余人,而没法扮演其余人的角色。这点在线solidity环境未来会作改进。安全
contract Coin { //关键字“public”使变量能从合约外部访问。 address public minter; mapping (address => uint) public balances; //事件让轻客户端能高效的对变化作出反应。 event Sent(address from, address to, uint amount); //这个构造函数的代码仅仅只在合约建立的时候被运行。 function Coin() { minter = msg.sender; } function mint(address receiver, uint amount) { if (msg.sender != minter) return; balances[receiver] += amount; } function send(address receiver, uint amount) { if (balances[msg.sender] < amount) return; balances[msg.sender] -= amount; balances[receiver] += amount; Sent(msg.sender, receiver, amount); } }
这个合约引入了一些新的概念,让咱们一个一个来看一下。网络
address public minter;
这行代码声明了一个可公开访问的状态变量,类型为address。address类型的值大小为160 bits,不支持任何算术操做。适用于存储合约的地址或其余人的公私钥。public关键字会自动为其修饰的状态变量生成访问函数。没有public关键字的变量将没法被其余合约访问。另外只有本合约内的代码才能写入。自动生成的函数以下:数据结构
function minter() returns (address) { return minter; }
固然咱们本身增长一个这样的访问函数是行不通的。编译器会报错,指出这个函数与一个状态变量重名。并发
下一行代码 mapping (address => uint) public balances;
建立了一个public的状态变量,可是其类型更加的复杂。该类型将一些address映射到无符号整数。mapping能够被认为是一个哈希表,每个可能的key对应的value被虚拟的初始化为全0.这个类比不是很严谨,对于一个mapping,没法获取一个包含其全部key或者value的链表。因此咱们得本身记着添加了哪些东西到mapping中。更好的方式是维护一个这样的链表,或者使用其余更高级的数据类型。或者只在不受这个缺陷影响的场景中使用mapping,就像这个例子。在这个例子中由public关键字生成的访问函数将会更加复杂,其代码大体以下:
function balances(address _account) returns (uint balance) { return balances[_account]; }
咱们能够很方便的经过这个函数查询某个特定帐号的余额。
event Sent(address from, address to, uint value);
这行代码声明了一个“事件”。由send函数的最后一行代码触发。客户端(服务端应用也适用)能够以很低的开销来监听这些由区块链触发的事件。事件触发时,监听者会同时接收到from,to,value这些参数值,能够方便的用于跟踪交易。为了监听这个事件,你可使用以下代码:
Coin.Sent().watch({}, '', function(error, result) { if (!error) { console.log("Coin transfer: " + result.args.amount + " coins were sent from " + result.args.from + " to " + result.args.to + "."); console.log("Balances now:\n" + "Sender: " + Coin.balances.call(result.args.from) + "Receiver: " + Coin.balances.call(result.args.to)); } }
注意在客户端中是如何调用自动生成的 balances 函数的。
这里有个比较特殊的函数 Coin。它是一个构造函数,会在合约建立的时候运行,以后就没法被调用。它会永久得存储合约建立者的地址。msg(以及tx和block)是一个神奇的全局变量,它包含了一些能够被合约代码访问的属于区块链的属性。msg.sender 老是存放着当前函数的外部调用者的地址。
最后,真正被用户或者其余合约调用,用来完成本合约功能的函数是mint和send。若是合约建立者以外的其余人调用mint,什么都不会发生。而send能够被任何人(拥有必定数量的代币)调用,发送一些币给其余人。注意,当你经过该合约发送一些代币到某个地址,在区块链浏览器中查询该地址将什么也看不到。由于发送代币致使的余额变化只存储在该代币合约的数据存储中。经过事件咱们能够很容易建立一个能够追踪你的新币交易和余额的“区块链浏览器”。
对于程序员来讲,区块链这个概念其实不难理解。由于最难懂的一些东西(挖矿,哈希,椭圆曲线加密,点对点网络等等)只是为了提供一系列的特性和保障。你只须要接受这些既有的特性,不须要关心其底层的技术。就像你若是仅仅是为了使用亚马逊的AWS,并不须要了解其内部工做原理。
区块链是一个全局共享的,事务性的数据库。这意味着参与这个网络的每个人均可以读取其中的记录。若是你想修改这个数据库中的东西,就必须建立一个事务,并获得其余全部人的确认。事务这个词意味着你要作的修改(假如你想同时修改两个值)只能被完彻底全的实施或者一点都没有进行。
此外,当你的事务被应用到这个数据库的时候,其余事务不能修改该数据库。
举个例子,想象一张表,里面列出了某个电子货币全部帐号的余额。当从一个帐户到另一个帐户的转帐请求发生时,这个数据库的事务特性确保从一个帐户中减掉的金额会被加到另外一个帐户上。若是由于某种缘由,往目标帐户上增长金额没法进行,那么源帐户的金额也不会发生任何变化。
此外,一个事务会被发送者(建立者)进行密码学签名。这项措施很是直观的为数据库的特定修改增长了访问保护。在电子货币的例子中,一个简单的检查就能够确保只有持有帐户密钥的人,才能从该帐户向外转帐。
区块链要解决的一个主要难题,在比特币中被称为“双花攻击”。当网络上出现了两笔交易,都要花光一个帐户中的钱时,会发生什么?一个冲突?
简单的回答是你不须要关心这个问题。这些交易会被排序并打包成“区块”,而后被全部参与的节点执行和分发。若是两笔交易相互冲突,排序靠后的交易会被拒绝并剔除出区块。
这些区块按时间排成一个线性序列。这也正是“区块链”这个词的由来。区块以一个至关规律的时间间隔加入到链上。对于以太坊,这个间隔大体是17秒。
做为“顺序选择机制”(一般称为“挖矿”)的一部分,一段区块链可能会时不时被回滚。但这种状况只会发生在整条链的末端。回滚涉及的区块越多,其发生的几率越小。因此你的交易可能会被回滚,甚至会被从区块链中删除。可是你等待的越久,这种状况发生的几率就越小。
以太坊虚拟机(EVM)是以太坊中智能合约的运行环境。它不只被沙箱封装起来,事实上它被彻底隔离,也就是说运行在EVM内部的代码不能接触到网络、文件系统或者其它进程。甚至智能合约与其它智能合约只有有限的接触。
以太坊中有两类帐户,它们共用同一个地址空间。外部帐户,该类帐户被公钥-私钥对控制(人类)。合约帐户,该类帐户被存储在帐户中的代码控制。
外部帐户的地址是由公钥决定的,合约帐户的地址是在建立该合约时肯定的(这个地址由合约建立者的地址和该地址发出过的交易数量计算获得,地址发出过的交易数量也被称做"nonce")
合约帐户存储了代码,外部帐户则没有,除了这点之外,这两类帐户对于EVM来讲是同样的。
每一个帐户有一个key-value形式的持久化存储。其中key和value的长度都是256比特,名字叫作storage.
另外,每一个帐户都有一个以太币余额(单位是“Wei"),该帐户余额能够经过向它发送带有以太币的交易来改变。
一笔交易是一条消息,从一个帐户发送到另外一个帐户(多是相同的帐户或者零帐户,见下文)。交易能够包含二进制数据(payload)和以太币。
若是目标帐户包含代码,该代码会执行,payload就是输入数据。
若是目标帐户是零帐户(帐户地址是0),交易将建立一个新合约。正如上文所讲,这个合约地址不是零地址,而是由合约建立者的地址和该地址发出过的交易数量(被称为nonce)计算获得。建立合约交易的payload被看成EVM字节码执行。执行的输出作为合约代码被永久存储。这意味着,为了建立一个合约,你不须要向合约发送真正的合约代码,而是发送可以返回真正代码的代码。
以太坊上的每笔交易都会被收取必定数量的gas,gas的目的是限制执行交易所需的工做量,同时为执行支付费用。当EVM执行交易时,gas将按照特定规则被逐渐消耗。
gas price(以太币计)是由交易建立者设置的,发送帐户须要预付的交易费用 = gas price * gas amount。 若是执行结束还有gas剩余,这些gas将被返还给发送帐户。
不管执行到什么位置,一旦gas被耗尽(好比降为负值),将会触发一个out-of-gas异常。当前调用帧所作的全部状态修改都将被回滚。
每一个帐户有一块持久化内存区域被称为存储。其形式为key-value,key和value的长度均为256比特。在合约里,不能遍历帐户的存储。相对于另外两种,存储的读操做相对来讲开销较大,修改存储更甚。一个合约只能对它本身的存储进行读写。
第二个内存区被称为主存。合约执行每次消息调用时,都有一块新的,被清除过的主存。主存能够以字节粒度寻址,可是读写粒度为32字节(256比特)。操做主存的开销随着其增加而变大(平方级别)。
EVM不是基于寄存器,而是基于栈的虚拟机。所以全部的计算都在一个被称为栈的区域执行。栈最大有1024个元素,每一个元素256比特。对栈的访问只限于其顶端,方式为:容许拷贝最顶端的16个元素中的一个到栈顶,或者是交换栈顶元素和下面16个元素中的一个。全部其余操做都只能取最顶的两个(或一个,或更多,取决于具体的操做)元素,并把结果压在栈顶。固然能够把栈上的元素放到存储或者主存中。可是没法只访问栈上指定深度的那个元素,在那以前必需要把指定深度之上的全部元素都从栈中移除才行。
EVM的指令集被刻意保持在最小规模,以尽量避免可能致使共识问题的错误实现。全部的指令都是针对256比特这个基本的数据类型的操做。具有经常使用的算术,位,逻辑和比较操做。也能够作到条件和无条件跳转。此外,合约能够访问当前区块的相关属性,好比它的编号和时间戳。
合约能够经过消息调用的方式来调用其它合约或者发送以太币到非合约帐户。消息调用和交易很是相似,它们都有一个源,一个目标,数据负载,以太币,gas和返回数据。事实上每一个交易均可以被认为是一个顶层消息调用,这个消息调用会依次产生更多的消息调用。
一个合约能够决定剩余gas的分配。好比内部消息调用时使用多少gas,或者指望保留多少gas。若是在内部消息调用时发生了out-of-gas异常(或者其余异常),合约将会获得通知,一个错误码被压在栈上。这种状况只是内部消息调用的gas耗尽。在solidity中,这种状况下发起调用的合约默认会触发一我的工异常。这个异常会打印出调用栈。就像以前说过的,被调用的合约(发起调用的合约也同样)会拥有崭新的主存并可以访问调用的负载。调用负载被存储在一个单独的被称为calldata的区域。调用执行结束后,返回数据将被存放在调用方预先分配好的一块内存中。
调用层数被限制为1024,所以对于更加复杂的操做,咱们应该使用循环而不是递归。
存在一种特殊类型的消息调用,被称为callcode。它跟消息调用几乎彻底同样,只是加载自目标地址的代码将在发起调用的合约上下文中运行。
这意味着一个合约能够在运行时从另一个地址动态加载代码。存储,当前地址和余额都指向发起调用的合约,只有代码是从被调用地址获取的。
这使得Solidity能够实现”库“。可复用的库代码能够应用在一个合约的存储上,能够用来实现复杂的数据结构。
在区块层面,能够用一种特殊的可索引的数据结构来存储数据。这个特性被称为日志,Solidity用它来实现事件。合约建立以后就没法访问日志数据,可是这些数据能够从区块链外高效的访问。由于部分日志数据被存储在布隆过滤器(Bloom filter) 中,咱们能够高效而且安全的搜索日志,因此那些没有下载整个区块链的网络节点(轻客户端)也能够找到这些日志。
合约甚至能够经过一个特殊的指令来建立其余合约(不是简单的向零地址发起调用)。建立合约的调用跟普通的消息调用的区别在于,负载数据执行的结果被看成代码,调用者/建立者在栈上获得新合约的地址。
只有在某个地址上的合约执行自毁操做时,合约代码才会从区块链上移除。合约地址上剩余的以太币会发送给指定的目标,而后其存储和代码被移除。
注意,即便一个合约的代码不包含自毁指令,依然能够经过代码调用(callcode)来执行这个操做。
若是你但愿高效的学习以太坊DApp开发,能够访问汇智网提供的最热门在线互动教程:
其余更多内容也能够访问这个以太坊博客。
转自: https://blog.csdn.net/mongo_node/article/details/80151896