Lucifaer's Blog.

以太坊简介

Word count: 2,387 / Reading time: 9 min
2018/06/18 Share

以太坊的目的是基于脚本、竞争币和链上元协议(on-chain meta-protocol)概念进行整合和提高,使得开发者能够创建任意的基于共识的、可扩展的、标准化的、特性完备的、易于开发的和协同的应用。以太坊通过建立终极的抽象的基础层-内置有图灵完备编程语言的区块链-使得任何人都能够创建合约和去中心化应用并在其中设立他们自由定义的所有权规则、交易方式和状态转换函数。域名币的主体框架只需要两行代码就可以实现,诸如货币和信誉系统等其它协议只需要不到二十行代码就可以实现。智能合约-包含价值而且只有满足某些条件才能打开的加密箱子-也能在我们的平台上创建,并且因为图灵完备性、价值知晓(value-awareness)、区块链知晓(blockchain-awareness)和多状态所增加的力量而比比特币脚本所能提供的智能合约强大得多。

上面是以太坊白皮书上对以太坊的介绍,其实简单来说,以太坊就是一个框架,利用这个框架开发出的应用叫智能合约。

0x00 以太坊账户

在以太坊系统中,状态是由被称为“账户”(每个账户是一个20字节的地址)的对象和在两个账户之间转移价值和信息的状态转换系统构成的。“账户”包含四个部分:

  • 随机数,用于确定每笔交易只能被处理一次的计数器
  • 账户目前的以太币余额
  • 账户的合约代码,如果有的话
  • 账户的存储(默认为空)

以太币(Ether)是以太坊内部的主要加密燃料,用于支付交易费用。一般而言,以太坊有两种类型的账户:外部所有的账户(由私钥控制的)和合约账户(由合约代码控制)。

外部账户可以简单理解为使用服务的用户,合约用户可以简单理解为提供服务的内部工作人员。

外部所有的账户没有代码,人们可以通过创建和签名一笔交易从一个外部账户发送消息。每当合约账户收到一条消息,合约内部的代码就会被激活,允许它对内部存储进行读取和写入,和发送其它消息或者创建合约。

0x01 以太坊的消息

以太坊的消息在某种程度上和比特币交易很像,区别在于以下三点:

  • 以太坊的消息可以由外部实体或者合约创建,然而比特币的交易只能从外部创建。
  • 以太坊消息可以选择包含数据。
  • 如果以太坊消息的接受者是合约账户,可以选择进行回应,这意味着以太坊消息也包含函数概念。

0x02 以太坊的交易

这部分我觉得干说不是很形象,用从论坛中的一位大哥的代码分析来形象的说明一下。首先要明确的一点,以太坊上的应用都是通过智能合约与区块链进行交互的,而只能合约的执行是由交易触发的,可以说在以太坊中,一切都源于交易。源码在https://github.com/ethereum。想看的话可以自己跟着这篇文章看。

1. 以太坊交易的数据结构

core/types/transaction.go中有交易的数据结构:

1
2
3
4
5
6
7
type Transaction struct {
data txdata
// caches
hash atomic.Value
size atomic.Value
from atomic.Value
}

可以看到只有data一个字段,其他三个字段都是缓存字段。txdata也是一个结构体,就在这段代码的下面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type txdata struct {
AccountNonce uint64 `json:"nonce" gencodec:"required"`
Price *big.Int `json:"gasPrice" gencodec:"required"`
GasLimit uint64 `json:"gas" gencodec:"required"`
Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value" gencodec:"required"`
Payload []byte `json:"input" gencodec:"required"`

// Signature values
V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`

// This is only used when marshaling to JSON.
Hash *common.Hash `json:"hash" rlp:"-"`
}

首先解释一下注释中的内容来源。以太坊会向外部提供JSON RPC服务,供外部调用,RPC服务通过json格式传输数据,节点接收到json数据后会转换成内部的数据结构来使用。两种数据结构转换的方法是利用json的序列化(MarshalJSON)和反序列化(UnmarshalJSON)来完成的。而注释的内容就是内部结构体与json数据各字段的对应关系。

为什么要使用json来进行通讯呢?因为web3.js中的eth.getTransaction()eth.sendTransaction()使用的数据就是json格式的。

接下来解释一下各字段的意思,其实大部分我们都能看懂:

  • AccountNonce:此交易的发送者已发送过的交易数。
  • Price:此交易的gas price
  • GasLimit:本交易允许消耗的最大gas数量
  • Recipient:交易的接收者,是一个地址
  • Amount:交易转移的以太币数量,单位是wei
  • Payload:交易可以携带的数据,在不同类型的交易中有不同的含义
  • V R S:交易的签名数据

这里出现了一个gas新名词,其实gas就相当于运行合约的手续费。gas的数量是由合约的复杂性来确定的,当然gas也是矿工在运行时所收取的“工资”。gas主要是用来先知执行交易所需要的工作量,无论运行到什么时候,只要gas被耗尽,就会触发异常,所有的状态修改帧都会回滚,这样就防止有人写出无法停止的合约来阻塞网络。

这里并没有一个字段来指明交易的发送者,因为交易的发送者地址可以从签名中得到。

Payload这个字段在eth.sendTransaction()中对应的是data字段,在eth.getTransaction()中对应的是input字段。

2. 交易的哈希计算

下面是计算交易Hash的函数,它是先从缓存tx.hash中取,如果取到,就直接返回,如果缓存中没有,就调用rlpHash计算hash,然后把hash值加入到缓存中。

1
2
3
4
5
6
7
8
9
10
// Hash hashes the RLP encoding of tx.
// It uniquely identifies the transaction.
func (tx *Transaction) Hash() common.Hash {
if hash := tx.hash.Load(); hash != nil {
return hash.(common.Hash)
}
v := rlpHash(tx)
tx.hash.Store(v)
return v
}

rlpHash代码如下:

1
2
3
4
5
6
func rlpHash(x interface{}) (h common.Hash) {
hw := sha3.NewKeccak256()
rlp.Encode(hw, x)
hw.Sum(h[:0])
return h
}

rlpHash函数可以看出,计算hash的方法是先对交易进行RLP编码,然后计算RLP编码数据的hash,具体的hash算法是Keccak256

而进行哈希计算的字段在rlp.Encode的注释中有所提及:

1
2
3
// If the type implements the Encoder interface, Encode calls
// EncodeRLP. This is true even for nil pointers, please see the
// documentation for Encoder.

如果一个类型实现了Encoder接口,那么Encode函数就会调用那个类型所实现的EncodeRLP函数。所以我们就要看Transaction这个结构体是否实现了EncodeRLP函数。回到core/types/transaction.go中,可以看到Transaction确实实现了EncodeRLP函数:

1
2
3
4
// DecodeRLP implements rlp.Encoder
func (tx *Transaction) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &tx.data)
}

从这可以看出交易的hash实际上是对tx.data进行hash计算得到的:txhash=Keccak256(rlpEncode(tx.data))

0x03 交易的类型

在源码中交易只有一种数据结构,就是在web3.js中定义的发送交易的接口,而根据接口的三种不同使用方法,可以大致将交易分为三种:转账交易、创建合约交易、执行合约交易。

1
web3.eth.sendTransaction(transactionObject [, callback])

1. 转账交易

转账是最简单的一种交易,这里转账是指从一个账户向另一个账户发送以太币。发送转账交易的时候只需要指定交易的发送者、接收者、转币的数量。使用web3.js发送转账交易应该像这样:

1
2
3
4
5
web3.eth.sendTransaction({
from: "0xb60e8dd61c5d32be8058bb8eb970870f07233155",
to: "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
value: 10000000000000000
});

value是转移的以太币数量,单位是wei,对应的是源码中的Amount字段。to对应的是源码中的Recipient

2. 创建合约的交易

创建合约指的是将合约部署到区块链上,这也是通过发送交易来实现。在创建合约的交易中,to字段要留空不填,在data字段中指定合约的二进制代码,from字段是交易的发送者也是合约的创建者。

1
2
3
4
web3.eth.sendTransaction({
from: "contract creator's address",
data: "contract binary code"
});

data字段对应的是源码中的Payload字段。

3. 执行合约的交易

调用合约中的方法,需要将交易的to字段指定为要调用的合约的地址,通过data字段指定要调用的方法以及向该方法传递的参数。

1
2
3
4
5
web3.eth.sendTransaction({
from: "sender's address",
to: "contract address",
data: "hash of the invoked method signature and encoded parameters"
});

data字段需要特殊的编码规则,具体细节可以参考Ethereum Contract ABI。自己拼接字段既不方便又容易出错,所以一般都使用封装好的SDK(比如web3.js)来调用合约。

CATALOG
  1. 1. 0x00 以太坊账户
  2. 2. 0x01 以太坊的消息
  3. 3. 0x02 以太坊的交易
    1. 3.1. 1. 以太坊交易的数据结构
    2. 3.2. 2. 交易的哈希计算
  4. 4. 0x03 交易的类型
    1. 4.1. 1. 转账交易
    2. 4.2. 2. 创建合约的交易
    3. 4.3. 3. 执行合约的交易