您的位置:首页 > 其它

以太坊geth学习小结

2018-01-15 21:20 176 查看
前言:一看历史记录发现自己已经一年多没有更新过博客了,有种时光易逝的感觉,既然来过了总要留下些自己的足迹,这一年经历了太多,或私事,或工作太忙,主要时自己太懒,以后将积极与大家分享我日常工作中的一些问题。大家遇到问题的,也可以给我留言,希望和大家一起讨论。
本文的个人有道笔记分享地址:http://note.youdao.com/noteshare?id=3f46784d7cc2d3ce2db14385ba07abaa
参考资料:
官方地址:https://geth.ethereum.org/
Geth的Github地址:https://github.com/ethereum/go-ethereum
一、安装geth客户端
1、快速安装
Ubuntu 16.04下已经提供了可用的PPA源,可以通过如下命令快速安装

sudo apt-get install software-properties-common sudo add-apt-repository -y ppa:ethereum/ethereum sudo apt-get updatesudo apt-get install ethereum

默认安装的是最新稳定版本,如果希望尝试最新版本可以安装ethereum-unstable。
也可以通过执行apt-get install geth只安装geth模块,此操作不会安装其它模块(bootnode,
evm, disasm, rlpdump, ethtest)。

+
2、源码编译安装
编译安装前需要先安装Go和C编译器,如果已经安装可以跳过
sudo apt-get install -y build-essential golang #安装Go和C编译器,如果已经安装可以跳过cd go-ethereummake geth
编译完成后,可执行build/bin/geth启动节点

安装参考资料:https://github.com/ethereum/go-ethereum/wiki/Installation-Instructions-for-Ubuntu

二、以太坊的网络
以太坊官方主要有生产网络、测试网络
1.)生产网络
以太坊的生产网络顾名思义,也就是产生真正有价值的 的以太币的网络。 目前生产网络目前已经生成了300万以上的区块,还在持续生成中。挖矿挖出来的每个以台币在10美金以上。
生产环境网络优点
1.全球化的,部署在Internet环境上的,
2.智能合约的代码,执行,区块的调用,都可以清晰的查看到。

3.部署在生产环境上的智能合约,全世界任何应用都可以调用
缺点:

1.任何合约执行都会消耗真实的以太币,也就是真实的现金。不适合开发、调试和测试,

2.所有节点是全球化的,速度较慢

3.且对于部分商业应用来说,只需要一部分节点,例如分布式部署的10-20台服务器即可。而不需要遍布全球的网络。

2.)测试网络
以太坊的测试网络也是官方提供的,顾名思义就是专供用户来开发、调试和测试的。 上面的合约执行不消耗真实的以太币。也就是不花钱
所以如果开发简单的智能合约的话,用测试网络也就足够了,用户可以把更多的精力集中在智能合约的编写上。
但因为网络是官方提供的,因此对于以太坊技术的底层实现、Geth的各种参数接口、整个以太坊的技术真实性能的理解就会弱很多。所以从开发的角度来说,一个更好的选择是Private Network,可以从技术的底层去深入理解以太坊
测试环境网络优点

1.合约执行不消耗真实货币

2.全球化的,部署在Internet环境上的,

3.智能合约的代码,执行,区块的调用,都可以清晰的查看到。

4.部署在测试环境上的智能合约,全世界任何应用都可以调用
缺点:

1.所有节点是全球化的,速度较慢

2.测试网络不可能作为商业应用的实际落地环境

3.)创建私有网络

1.建立目录和genesis.json

在命令行模式创建一个目录,如test

mkdir test

创建文件genesis.json,并填入如下内容

vim genesis.json
{
"config": {
"chainId": 10,
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"difficulty": "0x4000",
"alloc": {},
"coinbase": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "",
"gasLimit": "0xffffffff"
}

各参数说明:

mixhash
与nonce配合用于挖矿,由上一个区块的一部分生成的hash。注意他和nonce的设置需要满足以太坊的Yellow
paper, 4.3.4. Block Header Validity, (44)章节所描述的条件。.
nonce
nonce就是一个64位随机数,用于挖矿,注意他和mixhash的设置需要满足以太坊的Yellow
paper, 4.3.4. Block Header Validity, (44)章节所描述的条件。
difficulty
设置当前区块的难度,如果难度过大,cpu挖矿就很难,这里设置较小难度
alloc
用来预置账号以及账号的以太币数量,因为私有链挖矿比较容易,所以我们不需要预置有币的账号,需要的时候自己创建即可以。
coinbase
矿工的账号,随便填
timestamp
设置创世块的时间戳
parentHash
上一个区块的hash值,因为是创世块,所以这个值是0
extraData
附加信息,随便填,可以填你的个性信息
gasLimit
该值设置对GAS的消耗总量限制,用来限制区块能包含的交易信息总和,因为我们是私有链,所以填最大。
2.执行命令,创建创世区块

geth --datadir "~/test" init genesis.json

3.创建自己的私有链

geth --identity "privateetherum"  --rpc  --rpccorsdomain "*" --datadir "~/test" --port "30303"  --networkid 95518 console

//geth --datadir "~/test" --nodiscover console

启动参数说明:

identity
区块链的标示,随便填写,用于标示目前网络的名字
init
指定创世块文件的位置,并创建初始块
datadir
设置当前区块链网络数据存放的位置
port
网络监听的端口,可自定义,默认为30303
rpc
启动rpc通信,可以进行智能合约的部署和调试
rpcapi
设置允许连接的rpc的客户端,一般为db,eth,net,web3
networkid
设置当前区块链的网络ID,用于区分不同的网络,是一个数字
console
开启一个可交互的JavaScript终端
三、以太坊的基础知识
1.常用API
以太坊有很多api,本文演示一些常用的,更多api的用法可查阅官方wiki

新建账户
> personal.newAccount()

列举账户
> personal.listAccounts
或者
> eth.accounts

解锁账户
> personal.unlockAccount('xxxxx') //xxxxx账户地址

锁定账户
> personal.lockAccount('xxxxx')

获取资产
> eth.getBalance(eth.accounts[0]) //0时数组的索引,表示第一个账户

单位转换 
> web3.fromWei(eth.getBalance(eth.accounts[0]), 'Ether')

获取gas价格
> eth.gasPrice

发送一笔交易
> eth.sendTransaction({from:user1, to:user2, value:xxx})
//注意这里必须要先把user1,就原账户解锁才能执行,不然会报如下错误//eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:60000})

获取一笔交易的详情
> eth.getTransaction('xxxxx') //xxxx表示交易生成的单号

计算一笔交易所需要花费的gas
> eth.estimateGas({from:user1 to:user2, value:xxxx})

获取合约账户的代码
> eth.getCode('xxxx'); //xxxx时合约地址

开启挖矿
> miner.start()

停止挖矿
> miner.stop()

查看节点信息
> admin.nodeInfo

https://github.com/ethereum/go-ethereum/wiki/Managing-your-accounts

Ether币的基本单位
Ether币最小的单位是Wei,也是命令行默认的单位, 然后每1000个进一个单位,依次是

kwei (1000 Wei)

mwei (1000 KWei)

gwei (1000 mwei)

szabo (1000 gwei)

finney (1000 szabo)

ether (1000 finney)

以太坊虚拟机EVM
2.以太坊虚拟机(EVM)
以太坊虚拟机(EVM)是智能合约的运行环境。它是一个完全独立的沙盒,合约代码在EVM内部运行,对外是完全隔离的,甚至不同合约之间也只有有限的访问权限
账户

以太坊中有两种不同类型但是共享同一地址空间的账户:外部账户由一对公私钥控制,合约账户由账户内部的合约代码控制。

外部账户的地址是由公钥(经过hash运算)决定的,而合约账户的地址在此合约被创建的时候决定的(由合约创建者的地址和发送到此合约地址的交易数决定,这就是所谓的“nonce”)

不管是哪种类型的账户,EVM的处理方式是一样的

每个账户都有一个持久的key-value类型的存储,把256字节的key映射到256字节的value

此外,每个账户都有以“Wei”为单位,在交易过程中会被修改的资产(balance)信息
交易

交易是一个从账户发往另一个账户(可以是同一个账户或者是special zero-account)的消息。它包含二进制数据(交易相关的数据)和 Ether。

如果目标账户包含代码,代码会被执行,交易相关的数据将作为参数

如果目标账户是地址为0的账户zero-account, 交易会创建一个新的合约。如上文提到的,合约地址不是一个地址为0的地址,而是一个由交易发送者和交易数来决定的地址。这样的一笔(到zero-account)交易的相关参数会被转化为EVM字节码
然后被执行,输出结果就是被永久存储的合约代码。这意味着为了创建一个合约,并不需要发送真实的合约代码,代码可以被自动创建
gas

创建之后,每笔交易都需要一定数量的gas,用于限制交易所消耗的工作量,即交易是需要付出代价的(避免DDoS攻击)。EVM执行交易的过程中,gas会按一个特殊规则逐渐减少

费用的多少是由交易发起者设置,至少需要从发起账户支付gas_price * gas用费。如果交易执行完毕费用还有剩余的,将退回到发起账户。

如果交易完成之前费用耗尽,将会抛出一个out-of-gas的异常,所有的修改都会被回滚
更多关于gas的理解和讨论可以戳这里
storage,memory,stack

每个账户都有一个持久的内存空间,称之为storage,storage以key-value形式存储,256字节的key映射到256字节value,合约内部不可能枚举storage(内部元素),读取或者修改storage操作消耗都很大(原文是 It is not possible to enumerate storage from within a contract and it is comparatively costly to read and even more so, to modify storage.
)。 合约只能读取和修改自己的storage里的数据。

第二种内存空间称之为memory,里面存储着每个消息调用时合约创建的实例。memory是线型的,可以以字节级别来处理,但是限制为256字节宽度,写入可以是8或256字节宽度。当读取或写入一个预先未触发的指令的时候会消耗memory的空间,消耗空间的同时,必须支付gas。memory消耗的越多,需要的gas越多(按平方级增长)

EVM不是一个注册的机器而是一个堆栈机器,所以所有的计算指令都在stack空间里面执行。stack最多只能容纳1024个长度不超过256字节的指令元素。只能用下述方法,从顶部访问stack:可以拷贝最顶部的16个元素中的一个到stack的最顶部,或者将最顶部的那个元素与其下面的16个元素之一互换。所有其它操作从stack最顶部取出两个(或一个,或更多,取决于操作)元素,然后把结果push到stack顶端。当然将stack中的元素移到memory或者storage也是可以的,但是不能直接访问stack中间的元素(必须从头部开始访问)
指令集合

EVM的指令集合控制的很小,这样可以避免错误的执行引发问题。所有的指令都是操作最基本的数据类型,256字节。而且都是最常见的逻辑,算法,字节和比较运算。有条件或无条件的跳转都可以。此外,合约可以访问当前区块的属性,比如区块编号和时间戳。
消息调用

合约之间可以通过消息调用的方式进行相互调用或者另一个给另一个无合约账户(外部账户)转币。消息调用很像交易,两者都有源账户,目标账户,数据(data payload),Ether,费用和返回数据。实际上每笔交易都由一个可创建更多调用的顶级调用组成。

合约可以决定内部消息调用的时候发送多少手续费,保留多少。如果在内部消息调用的时候抛出out-of-gas异常(或者其它异常),这个会被一个错误值标记,放到stack顶部。如此,只有和消息一起发出的手续费才会被消耗。在Solidity中这种情形默认会引发一个异常,以便异常“冒泡”到stack最顶端

如上所述,被调用的合约会接收到一个刚创建的memory实例,并且可以访问调用参数,调用参数被存储在一个被为calldata的隔离的区域。执行完毕后,被调用的合约将返回数据存储在调用合约预先创建的内存中。

调用被限制在1024深度,这意味着复杂的操作应尽量使用循环代替递归调用。
代理调用/调用代码和库

存在一种称为delegatecall的特殊的多样性的消息调用,which is identical to a message call apart from the fact that the code at the target address is executed in the context of the calling contract and msg.sender and msg.value do not change their values.

这意味着合约可以在运行的时候动态的从另一个地址加载代码。存储、当前地址和资产仍然和调用的合约相关联,只有代码来自被调用的地址。

这样可以实现Solidity库的特性:反复使用的库代码可以被应用到合约的storage来实现复杂的数据结构。
日志
It is possible to store data in a specially indexed data structure that maps all the way up to the block level. This feature called logs is used by Solidity in order to implement events. Contracts
cannot access log data after it has been created, but they can be efficiently accessed from outside the blockchain. Since some part of the log data is stored in bloom filters, it is possible to search for this data in an efficient and cryptographically secure
way, so network peers that do not download the whole blockchain (“light clients”) can still find these logs.
Create

合约甚至可以使用特殊的opcode创建其它的合约,create消息调用和普通的消息调用区别在于,create消息调用的data字段会被执行,执行结果以代码的形式存储,调用者可在stack上接接到新合约账户的地址
自毁

合约代码从区块链上消失的唯一方式,就是合约账户执行自毁(selfdestruct)操作。

账户剩余的资产会被转移到指定的目标地址,然后存储(storage)和代码都会被删除

以太坊智能合约
与比特币相比,以太坊最大的不同点是:它可以支持更加强大的脚本语言(用技术语言讲就是图灵完备的脚本语言),允许开发者在上面开发任意应用,实现任意智能合约,这也是以太坊的最强大之处。作为平台,以太坊可以类比于苹果的应用商店,任何开发者都可以在上面开发应用,并出售给用户。
以太坊智能合约的金融应用,每一类金融合约都可以程序代码的形式写成智能合约。
开发语言
开发以太坊智能合约的语言主要有以下几种

Serpent 语法类似于Python

Solidity 语法类似于JavaScript

Mutan 语法类似于Go

LLL 语法类似于Lisp

编写第一个智能合约

我们一般用Solidity语言去编写约合,这是一个新生的语言,语法也在不断地完善,可能你在网上copy的代码,在你实际编译的过程中会报各种语法错误,这是正常现象,不要慌,稳住,按照语法错误提示去修改,然后你的第一份合约就可以顺利地跑起来了。

这里列举一个最简单的HelloWorld 合约,目的让大家了解合约运行的大概流程。
1.)使用智能合约的在线编译器https://ethereum.github.io/browser-solidity/编译以下代码

pragma solidity ^0.4.0;

contract HelloWorld{

address creator;

string greeting;

function HelloWorld(string _greeting) public{

creator=msg.sender;

greeting=_greeting;

}

function greet() public constant returns(string){

return greeting;

}

function setGreeting(string _newGreeting)public{

greeting=_newGreeting;

}

}
我们这里采用web3deploy部署方式

编译器生成的代码如下
var _greeting = /* var of type string here */ ;var helloworldContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"_newGreeting","type":"string"}],"name":"setGreeting","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"greet","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_greeting","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]);var
helloworld = helloworldContract.new( _greeting, { from: web3.eth.accounts[0], data: '0x6060604052341561000f57600080fd5b60405161041f38038061041f83398101604052808051820191905050336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060019080519060200190610081929190610088565b505061012d565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100c957805160ff19168380011785556100f7565b828001600101855582156100f7579182015b828111156100f65782518255916020019190600101906100db565b5b5090506101049190610108565b5090565b61012a91905b8082111561012657600081600090555060010161010e565b5090565b90565b6102e38061013c6000396000f30060606040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a413686214610051578063cfae3217146100ae575b600080fd5b341561005c57600080fd5b6100ac600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509190505061013c565b005b34156100b957600080fd5b6100c1610156565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101015780820151818401526020810190506100e6565b50505050905090810190601f16801561012e5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600190805190602001906101529291906101fe565b5050565b61015e61027e565b60018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101f45780601f106101c9576101008083540402835291602001916101f4565b820191906000526020600020905b8154815290600101906020018083116101d757829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061023f57805160ff191683800117855561026d565b8280016001018555821561026d579182015b8281111561026c578251825591602001919060010190610251565b5b50905061027a9190610292565b5090565b602060405190810160405280600081525090565b6102b491905b808211156102b0576000816000905550600101610298565b5090565b905600a165627a7a7230582046a4d75a8d115e50ac830243c4db27cd89a2c36e92ebc0097d4d4b272864616f0029',gas:
'4700000' }, function (e, contract){ console.log(e, contract); if (typeof contract.address !== 'undefined') { console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
} })
把上面的红色字体部分改一下,打招呼内容改为HelloWorld!, gas就是费用改小一点,不改也行,然后讲上面的这段编译后的代码复制到交互式窗口上面去,注意,在这之前你要先给你的当前账户解锁。
personal.unlockAccount(xxx)
2.)复制完出现下面红框中提示,submitted contract creation.... 表明合约已经提交了

3.)这时,我们还需要启动挖矿miner.start(),需要矿工将合约写到区块中,在这一步中,用户可获得合约的地址,以及调用合约所需的接口,以便之后使用。当看到contract mined! address ... 时,表明
合约已被开采,合约代码发布成功。

4.)运行合约,helloworld.greet(),返回HelloWorld!

下一篇将与大家一起分享etcd中raft共识,一起走读它的源码,分享如何使用raft包
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: