Professional Documents
Culture Documents
3 Ethereum Slides
3 Ethereum Slides
www.juzix.net
以太坊与智能合约 www.juzix.net
1. 以太坊的产生背景
2. 以太坊的发展历程
3. 以太坊的整体架构
4. 以太坊数据结构
5. 以太坊交易实现
6. 以太坊区块实现
7. 虚拟机和智能合约的发展
8. 智能合约开发
9. 以太坊虚拟机实现
10. TheDao事件分析
11. 以太坊2.0
12. PlatON
1. 以太坊的产生背景 www.juzix.net
大都会
(Metropolis)
3. 以太坊的整体架构 www.juzix.net
4. 以太坊数据结构-账户 www.juzix.net
外部账户和合约账户对比:
账户类型
账户构成 外部账户 合约账户
账户私钥 一般在以太坊的钱包客户端创建通过私钥 没有对应的账户私钥
算法创建生成,私钥由账户所有者自己保
管存储
账户地址 账户地址基于账户的私钥采用地址生成算 账户地址采用智能合约发布者的账户相关信息推
法推导得出 导得出
账户链上生成 通过账户私钥签名一笔交易发往以太坊的 通过外部账户向以太坊发布智能合约时创建生成
节点创建生成
账户存储 仅仅存储一个账户对应的交易序号和账户 除了外部账户的存储信息外,还存储该账户对应
的有效余额 的智能合约数据。这些智能合约的数据按照特定
的编码方式进行组织和存储
账户对应的数据结构,定义在core/state/state_object.go文件中:
type Account struct {
Nonce uint64 // 账户交易序号
Balance *big.Int // 账户余额,单位是wei
Root common.Hash // 智能合约数据存储的默克尔树根节点hash值
CodeHash []byte // 智能合约二进制代码的hash值
}
type stateObject struct {
address common.Address
addrHash common.Hash // hash of ethereum address of the account
data Account
db *StateDB
......
// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
code Code // contract bytecode, which gets set when code is loaded
......
}
4. 以太坊数据结构-账户 www.juzix.net
执行交易的时候创建账户逻辑,core/state_transition.go:
func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {
......
if contractCreation {
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) // 这里将生成合约账户
} else {
// Increment the nonce for the next transaction
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value) // 这里将在链上生成外部账户
}
......
}
从core/vm/evm.go中,可以看出通过caller地址和nonce生成合约地址
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr
common.Address, leftOverGas uint64, err error) {
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr)
}
4. 以太坊数据结构-区块 www.juzix.net
区块:记录一段时间内发生的交易和状态结果的数据结构,是对当前账本的一次共识。
4. 以太坊数据结构-区块 www.juzix.net
# /core/types/block.go
type Block struct {
header *Header // 区块头信息
uncles []*Header // 叔区块头列表
transactions Transactions // 区块包含的交易列表
......
}
type Header struct {
ParentHash common.Hash // 父区块hash
UncleHash common.Hash // 叔区块列表hash
Coinbase common.Address // 矿工账户, 区块奖励和打包费用
Root common.Hash // 状态默克尔树根节点hash
TxHash common.Hash // 区块体交易列表的默克尔树根节点hash值
ReceiptHash common.Hash // 收据默克尔树根节点hash
Difficulty *big.Int // 区块难度值
Number *big.Int // 区块编号
Nonce BlockNonce // 区块随机数
...... // GasLimit 区块gas费用的上限, GasUsed区块交易执行已经消耗的gas费用总和
}
4. 以太坊数据结构-交易 www.juzix.net
交易是一条外部账户发送到区块链上另一账户的消息的签名数据包。交易定义在core/types/transaction.go中
type Transaction struct {
data txdata
......
}
type txdata struct {
AccountNonce uint64 // 用来区别同一用户发出的不同交易的标记
Price *big.Int // 表示发送者愿意支付给矿工的Gas价格
GasLimit uint64 // 交易允许消耗的最大Gas数量
Recipient *common.Address // 交易接受者的地址,如果为空表示是一个合约创建交易
Amount *big.Int // 发送者要转移给接受者的以太币数量
Payload []byte // 数据字段,如果存在,表明该交易是一个创建或者调用智能合约交易
V *big.Int // 签名数据V值,用于恢复公钥
R *big.Int // 签名的R值
S *big.Int // 签名的S值
......
}
5. 以太坊交易实现 www.juzix.net
节点A 节点B
客户端1 1.发送交易
HttpServer 交易 6.执行交易 区块
2.构造交易
3.验证导入交易 5.添加交易到区块
SendTransaction
SendTx
AddLocal
AddLocals AddRemotes
addTxs
addTxsLocke
dadd
requestPromoteExecutables
runReorg
发送 NewTxsEvent
BroadcastTxs
5. 以太坊交易实现 www.juzix.net
用户通过JSON RPC发起eth_sendTransaction请求,会调用PublicTransactionPoolAPI的SendTransaction:
#internal/ethapi/api.go
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
......
if args.Nonce == nil {
// Hold the addresse's mutex around signing to prevent concurrent assignment of
// the same nonce to multiple accounts.
s.nonceLock.LockAddr(args.From)
defer s.nonceLock.UnlockAddr(args.From)
}
......
// Assemble the transaction and sign with the wallet
tx := args.toTransaction() //创建交易
signed, err := wallet.SignTx(account, tx, s.b.ChainConfig().ChainID) //对交易进行签名
if err != nil {
return common.Hash{}, err
}
return SubmitTransaction(ctx, s.b, signed) //提交到交易池
}
5. 以太坊交易实现 www.juzix.net
再看看SubmitTransaction函数:
#internal/ethapi/api.go
func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
if err := b.SendTx(ctx, tx); err != nil {
return common.Hash{}, err
}
......
}
#eth/api_backend.go
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
return b.eth.txPool.AddLocal(signedTx)
}
#core/tx_pool.go
func (pool *TxPool) addTxs(txs []*types.Transaction, local, sync bool) []error {
......
errs, dirtyAddrs := pool.addTxsLocked(txs, local) //交易放入queue列表中
......
done := pool.requestPromoteExecutables(dirtyAddrs) //从queue中选取一些交易放入pending列表中等待执行
......
}
5. 以太坊交易实现 www.juzix.net
pool.addTxsLocked会调用到下面函数
#core/tx_pool.go
func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err error) {
......
if err := pool.validateTx(tx, local); err != nil { //验证签名、余额、nonce等
log.Trace("Discarding invalid transaction", "hash", hash, "err", err)
invalidTxMeter.Mark(1)
return false, err
}
// If the transaction pool is full, discard underpriced transactions,当txpool已满,剔除掉低油价的交易
// 如果低于最低价,直接丢弃该交易返回。如果高于最低价,则从txpool中剔除一些低价的交易
if uint64(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
......
// New transaction is better than our worse ones, make room for it
drop := pool.priced.Discard(pool.all.Count()-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1),
pool.locals)
for _, tx := range drop {
......
pool.removeTx(tx.Hash(), false)
}
}
5. 以太坊交易实现 www.juzix.net
pool.requestPromoteExecutables会调用下面函数
#core/tx_pool.go
func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirtyAccounts *accountSet, events
map[common.Address]*txSortedMap) {
......
//把交易从queue列表“提拔”到pending列表
promoted := pool.promoteExecutables(promoteAddrs)
for _, tx := range promoted {
......
events[addr].Put(tx)
}
......
// Notify subsystems for newly added transactions
if len(events) > 0 {
......
//发送Event
pool.txFeed.Send(NewTxsEvent{txs})
}
}
5. 以太坊交易实现 www.juzix.net
handler.go将把交易广播给其他节点。
#eth/handler.go
func (pm *ProtocolManager) txBroadcastLoop() {
for {
select {
case event := <-pm.txsCh:
pm.BroadcastTxs(event.Txs)
节点A 节点B
5.验证区块
BlockValidator body和状态 BlockChain 6.保存数据 LevelDB
BlockChain
4.验证区块头
3.写入区块链
worker ethash
Block 2.共识区块 验证执行区块
1..执行交易生成临时区块
8. 广播区块 P2P
7.发送事件 P2P
6. 以太坊区块实现 www.juzix.net
commitNewWork
ApplyTransaction
commit
FinalizeAndAssemble
Seal
发送消息
WriteBlockWithState
发送NewMinedBlockEvent
BroadcastBlock
6. 以太坊区块实现 www.juzix.net
先看看miner/worker.go中的commitNewWork函数:
func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) {
......
pending, err := w.eth.TxPool().Pending() //取交易池中pending的交易
......
if len(localTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs)
if w.commitTransactions(txs, w.coinbase, interrupt) { // 执行本地交易
return
}
}
if len(remoteTxs) > 0 {
txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs)
if w.commitTransactions(txs, w.coinbase, interrupt) { // 执行远程交易
return
}
}
w.commit(uncles, w.fullTaskHook, true, tstart) // 提交交易
}
6. 以太坊区块实现 www.juzix.net
commit函数中会发送taskCh消息,处理taskCh消息代码如下所示:
case task := <-w.taskCh:
......
if err := w.engine.Seal(w.chain, task.block, w.resultCh, stopCh); err != nil { // 共识引擎共识区块
log.Warn("Block sealing failed", "err", err)
}
共识引擎共识成功后,resultCh会收到消息代码如下:
case block := <-w.resultCh:
......
stat, err := w.chain.WriteBlockWithState(block, receipts, task.state) // 写入区块链
......
w.mux.Post(core.NewMinedBlockEvent{Block: block}) // 发送事件给P2P模块
......
7. 虚拟机和智能合约的发展 www.juzix.net
与传统合约相比,区块链智能合约有如下优势:
1. 逻辑的明确性,不容易产生歧义。
2. 不可能被篡改。合约执行记录,可以作为永久凭证。
3. 合约执行的强制力可以保证。
Wasm:
EVM:
支持流行的语言开发智能合约.
只支持特定开发语言
受益于十多年来LLVM编译器优化
调试和测试比较困难
高性能: 每秒5000多次执行
有许多限制,如:堆栈(深度1024)、浮点数、小于
各大公司不断发展,如: Google, Apple, Microsoft,
256位整数
Mozilla, and Facebook
很少有人能够扩展EVM和所需的工具
CPU 级别直接支持64位和32位整数运算
7. 虚拟机和智能合约的发展 www.juzix.net
dapps:https://www.stateofthedapps.com/
优势:公开透明,去中心化,具有激励机制
8. 智能合约开发 www.juzix.net
智能合约使用Solidity编写,使用Solidity编译器编译成字节码。
Solidity提供了一个集成开发环境--Remix(http://remix.ethereum.org)
智能合约操作:
1. 创建合约:
编译 发起 部署 以太坊区
编写合约 字节码 交易
块链网络
2. 调用合约:
合约调用 调用 以太坊区
交易 块链网络
8. 智能合约开发 www.juzix.net
Solidity:Solidity是一种用于编写智能合约的高级语言,语法类似于JavaScript。(https://solidity.readthedocs.io)
1. Solidity中的合约与面向对象编程语言中的类很相似,在一个合约中可以声明多种成员,包括状态变量、函数、
函数修改器、事件等。同时,一个合约可以继承另一个合约。
2. 状态变量是永久存储在合约账户存储中的值,用于保存合约的状态。Solidity语言提供了多种类型的变量。
3. 函数是合约的执行单位,一个合约可能包含各种功能函数,它们相互调用,共同组成了合约的工作逻辑。
4. 函数修改器可用于改变函数的行为,在函数执行前插入其他逻辑。
5. 事件用于记录合约执行过程中发生的各种事件和状态变化。
contract Simple {
uint public data;
address public creater;
constructor() public {
creater = msg.sender;
}
modifier onlyCreater() {
require(msg.sender == creater);
_;
}
function testFun() onlyCreater public {
emit Transfer(msg.sender);
}
event Transfer(address indexed _from);
}
8. 智能合约开发 www.juzix.net
通过 http://remix.ethereum.org IDE和metamask演示ERC20合约的编写,部署和调用。合约部分代码如下:
contract TTEST {
string public name = "TTEST"; // token name
string public symbol = "TTEST"; // token symbol
uint256 public decimals = 6; // token decimals number
uint256 public totalSupply = 0; // token totalSupply
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;
address public owner = address(0);
constructor (uint256 initialSupply) public {
owner = msg.sender;
totalSupply = initialSupply;
balanceOf[msg.sender] = initialSupply;
emit Transfer(address(0), msg.sender, initialSupply);
}
function transfer(address _to, uint256 _value) public returns (bool success) {
require(_to != address(0)); // Prevent transfer to 0x0, use burn() instead
require(balanceOf[msg.sender] >= _value);
require(balanceOf[_to] + _value >= balanceOf[_to]); // Check for _value negative and add overflow
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}}
8. 智能合约开发 www.juzix.net
Truffle:合约编译、部署、测试。JavaScript编写的Node.js项目。
web3.js:https://web3js.readthedocs.io/en/1.0/,通过web3接口与节点交互。
安全性:
1. 由于我们将使用智能合约来持有代币
2. 所有智能合约都被公开的执行,源码很容易获得,代码中隐藏的Bug和漏洞也更容易被恶意攻击者发现。
9. 以太坊虚拟机实现 www.juzix.net
1. EVM是以太坊中的堆栈指令解释执行器,提供图灵完备的执行环境。
2. 由于智能合约的执行结果需要在以太坊的网络节点之间进行共识,因此EVM的所有指令执行必需具有确定性的输出。
3. EVM为了保证交易的可终止性,引入了执行器燃料Gas,按照执行指令的复杂度、所需要的内存空间等对每个
步骤进行计价,消耗指定数量的Gas,当交易所赋予的Gas消耗为零时则停止指令的执行,退出执行环境。
EVM虚拟机部分指令如下所示:
9. 以太坊虚拟机实现-指令 www.juzix.net
以太坊的EVM虚拟机是一种堆栈指令解释执行的执行器,通过从区块链的数据库中加载智能合约的二进制代码,
从头开始执行指令,结合交易请求的相关参数和相关的跳转指令来实现智能合约执行流程。
开始 判断并调整
堆栈空间
初始化指令 判断指令
信息 类型
调用设置的
拷贝合约代 指令执行
是
码 Callback
优化合约代 结束
码和跳转指 更新内存空
令 间
否
获取指令参
执行指令
数
9. 以太坊虚拟机实现-执行费用 www.juzix.net
1. 为了能够激励节点有足够的动力来维护以太坊网络的稳定运行,对提供节点算力的用户需要支付一定的奖励,这种
奖励来源于两份方面:一是单纯的区块打包奖励;一是打包交易的手续费。
2.交易手续费在以太坊中通过交易执行的复杂度来定价,这种定价取决于执行交易需要消耗多少算力。以太坊的交易
手续费也从一定层度上保证了智能合约不会出现执行后无法终止的情况,通过经济的手段防止了恶意的交易在区块链
网络上传播从而引起网络瘫痪。
3.以太坊的VM除了执行指令消耗Gas外,还对存储空间的使用也计入可Gas的计价范畴。消耗的存储空间越大,那么
需要支付的Gas费用也会越高。这个存储空间包括内存空间和磁盘空间,磁盘空间指的是智能合约的Storage变量。
9. 以太坊虚拟机实现-源码 www.juzix.net
先看看虚拟机解释器文件core/vm/interpreter.go文件的Run函数:
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
......
for atomic.LoadInt32(&in.evm.abort) == 0 { //循环取每一个指令
......
op = contract.GetOp(pc)
......
if !contract.UseGas(operation.constantGas) { // 计算指令gas消耗
return nil, ErrOutOfGas
}
......
if operation.dynamicGas != nil { // 计算内存gas消耗
cost, err = operation.dynamicGas(in.gasTable, in.evm, contract, stack, mem, memorySize)
if err != nil || !contract.UseGas(cost) {
return nil, ErrOutOfGas
}
}
......
res, err = operation.execute(&pc, in, contract, mem, stack) // 执行指令
......
}
return nil, nil
}
9. 以太坊虚拟机实现-源码 www.juzix.net
再看看PUSH指令实现core/vm/instructions.go文件的opPush1函数:
func opPush1(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
var (
codeLen = uint64(len(contract.Code))
integer = interpreter.intPool.get()
)
*pc += 1
if *pc < codeLen {
stack.push(integer.SetUint64(uint64(contract.Code[*pc])))
} else {
stack.push(integer.SetUint64(0))
}
return nil, nil
}
指令定价级别(core/vm/gas.go):
const (
GasQuickStep uint64 = 2
GasFastestStep uint64 = 3
GasFastStep uint64 = 5
GasMidStep uint64 = 8
GasSlowStep uint64 = 10
GasExtStep uint64 = 20
)
10. TheDao事件分析 www.juzix.net
The Dao可谓是以太坊上的一个明星项目,因为该项目通过公开募集的方式众筹到了1.6亿美元,一跃成为当时最大
的众筹项目而出名。将该项目超过30%的募集资金多达360多万的以太币转移到了黑客指定的账户中。
The Dao合约的splitDAO函数是这个攻击漏洞的主入口,该函数的目的是从原有的Dao中分裂出一个小规模的子Dao,
攻击者利用了这一可行的提取以太币的机制,通过创建子Dao来将以太币持续的转入指定的账户。
splitDao的相关漏洞代码如下:
10. TheDao事件分析 www.juzix.net
在上述代码中,先调用withdrawRewardFor函数转移以太币给交易发送方,然后再通过设置totalSupply、balances、
paidOut等参数来销毁对应的DAO代币。这可能导致多次转出以太币至指定的账户。
通过查看withdrawRewardFor的代码也存在相关的漏洞,withdrawRewardFor代码如下:
function withdrawRewardFor(address _account) noEther internal returns (bool _success)
{
if ((balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply < paidOut[_account])
throw;
uint reward =
(balanceOf(_account) * rewardAccount.accumulatedInput()) / totalSupply - paidOut[_account];
if (!rewardAccount.payOut(_account, reward))
throw;
paidOut[_account] += reward;
return true;
}
在上述代码中,对paidOut的更新发生在payout方法的调用之后,同样意味着可以通过竞争的方式多次调用
payout方法。payout方法的实现刚好又满足了这样的情况。下面给出payout的方法实现。
10. TheDao事件分析 www.juzix.net
上述代码的实现主要是通过call方法的调用向指定的账户转移以太币。攻击则是从这个点开始的:
1. 在以太坊中根据solidity的规范,如果向一个合约账户发送不带任何参数的请求,则fallback函数将在找不到对应的
处理函数时自动执行。上述代码中的call方法对应的地址为一个钱包合约地址,并且实现了一个fallback函数,在
fallback函数中再次调用splitDao方法,进入这个递归循环调用的过程。
2. 由于上述代码中的call方法没有明确的指定Gas费用,意味着当前剩余的Gas会被用于这次Call调用,因此递归调用
的次数就受限于攻击者发送原始交易时指定的Gas。
10. TheDao事件分析 www.juzix.net
从上述代码实现来看,这个攻击漏洞主要存在如下两个问题:
1. 合约代码不合理的执行次序
在展示的代码中,可以看到两处的代码执行顺序存在问题,账户余额的变更和账户转账操作应该遵循先变更账户后
转账的逻辑。
2. 未知代码不受限制的执行
在智能合约中要谨慎使用call或者delegatecall操作,因为这样的操作可能会导致一些未知的恶意代码执行,
需要确保被调用地址的合约实现逻辑是正确合法的,同时也最好限制有效的Gas费用,注意fallback方法的执行。
由于The Dao的合约机制,攻击者转移控制的以太币需要在一定的时间后才可以被拿走,这给该次的事件留下了
一定的缓冲处理时间。最终的机制是通过硬分叉的机制,将The Dao智能合约中的以太币转移到了一个新的账户中,
进而返还给众筹的参与者。最后以太坊社区未能达成统一的意见,系统在1920000区块节点分叉成以太坊经典ETC和
现有的以太坊ETH。
11. 以太坊2.0 www.juzix.net
以太坊执行交易的性能一直成为整个系统的瓶颈。分片(sharding)技术就是为了解决所有区块链面临的扩展性问题。
分片之后,每个节点只需要存储、处理一部分交易,从而解决区块链面临的扩展性问题。跨分片交易会引入复杂度。
POW由于消耗大量的算力和电力,以太坊的POS共识协议称为Casper。可以更加去中心化,高能效,经济安全。
以太坊的一位开发者曾经表示:“其实在最开始,大家并没有对以太坊虚拟机做过很深入的规划”。“以太坊虚拟机只
不过被当作像瑞士军刀那样的工具看待-----用处不小,但并不完美”。
eWASM 将是以太坊版本的 WASM (WebAssembly 的缩写)。eWASM 不确定时间。
11. 以太坊2.0 www.juzix.net
官网:https://platon.network
12. PlatON www.juzix.net
使用流行的WASM 库支持:
特性:
C++ 14 标准
支持多种语言.
JSON 库
每秒5000多次执行.
以太坊RLP协议
wasm合约, 隐私合约和可验证合约具有一致性开发体验.
部分boost库
12. PlatON www.juzix.net
通过可验证计算传递信任:
计算节点产生证明 (𝛑𝛑)
𝛑𝛑的验证比计算 f(x)要快很多
PlatON将计算与共识分离:
计算网络增加了区块链的无信任
计算能力.
区块链仅负责验证.
12. PlatON www.juzix.net
隐私合约使用高级语言编写,编译成WASM代码部署到区块链上.
具有预付计算费用的交易触发隐私合约的执行,数据提供者本地提供输入数据.
结果返回到区块链验证,验证通过就结算费用.
12. PlatON www.juzix.net
共识机制:PPOS + CBFT
节点质押,用户
利用VRF选取25名 利用CBFT共 结算周期获取
委托,选取101名
当前轮验证人节 识轮流出块。 质押奖励
验证人
点 获得出块奖励
为了数据的流动
矩阵元
www.juzix.net