深入学习以太坊(以太坊交易/EVM)

区块链

以太坊交易

1. 以太坊交易

交易是由外部拥有的账户发起的签名信息,由以太坊网络传输,并被序列化后记录在以太坊区块链上。

notes:

  1. 交易是唯一可以出发状态更改或导致合约在EVM中执行的事物。
  2. 以太坊是一个全局单例状态机,交易是唯一可以改变其状态的东西。
  3. 合约不是自己运行的,以太坊上一切变化起始于交易。
  • 交易的数据结构交易是包含一下数据的序列化二进制消息:nonce:由发起人EOA发出的序列号,用于防止交易消息重播。

    gas price:交易发起人愿意支付的gas单价。

    start gas:交易发起人愿意支付的最大gas量。

    to:目的以太坊地址。

    value:要发送到木的地的以太数量。

    data:可变长度二进制数据负载(payload)。

    v,r,s:发起人EOA的ECDSA签名的三个组成部分。

    交易消息的结构使用递归长度前缀(RLP)编码方案进行序列化,该方案专为在以太坊中准确和字节完美的数据序列化而创建。

  • nonce黄皮书定义:一个标量值,等于从这个地址发送的交易数,或者对于关联code的账户来说,是这个账户创建合约的数量。nonce不会明确存储为区块链中账户状态的一部分。相反,它是通过计算发送地址的已确认交易的数量来动态计算的。

    nonce值用于防止错误计算账户余额。nonce强制来自任何地址的交易按顺序处理,没有间隔,无论节点接受它们的顺序如何。

    使用nonce确保所有节点计算相同的余额个正确的序列交易,等同于用于防止比特币“重放攻击”的机制。

  • 并发和nonce以太坊是一个允许操作并发的系统,但强制执行单例状态。例如,出块的时候只有一个系统状态。假如我们有多个独立钱包应用或客户端,比如MetaMask和Geth,它们可以使用相同的地址生成交易。

    如果我们希望多个钱包能够同时发送交易,如何设置nonce?

    • 用一台服务器为各个应用分配nonce,先来先服务--可能出现单点故障,并且失败的交易会将后续交易阻塞。
    • 生成交易后不分配nonce,也不签名,而是把它放入一个队列等待。另起一个节点跟踪nonce并签名交易。同样会有单点故障的可能,而且跟踪nonce和签名的节点是无法实现真正并发的。
  • 交易中的gas
    • 当由于交易或消息触发EVM运行时,每个指令都会在网络的每个节点上执行。这具有成本:对于每个执行的操作,都存在固定的成本,我们把这个成本用一定量gas表示。
    • gas是交易发起人需要为EVM上的每项操作支付的成本名称。发起交易时,我们需要从执行代码的矿工那里用以太币购买gas。
    • gas与消耗的系统资源对应,这是具有自然成本的。因此在设计上gas和ether有意地解耦,消耗的gas数量代表了对资源的占用,而对应的交易费用则还跟gas对以太的单价有关。这两者是由自由市场调节的:gas的价格实际上由矿工决定,他们可以拒绝处理gas家底低于最低限额的交易。我们不需要专门购买gas,只需将以太币添加到账户即可,客户端在发送交易时会自动用以太币购买gas。而以太币本身价格通常由于市场力量而波动。
  • gas的计算
    • 发起交易时的gas limit并不是要支付的gas数量,而只是给定了一个消耗gas的上限,相当于“押金”
    • 实际交付的gas数量是执行过程中消耗的gas,gas limit中剩余的部分会返回给发送人
    • 最终支付的gas费用是gasUsed对应的以太币费用,单价由设定的gasPrice而定
    • 最终支付费用totalCost=gasPrice*gasUsed
    • totalCost会作为交易手续费(Tx fee)支付给矿工

    估计要花费的gas:使用客户端的eth.estimateGas()方法估计

  • 交易的接收者(to)
    • 交易接受者在to字段中指定,是一个20字节的以太坊地址。地址可以是EOA或合约地址。
    • 以太坊没有进一步的验证,任何20字节的值都被认为是有效的。如果20字节值对应于没有相应私钥地址,或不存在的合约,则该交易仍然有效。以太坊无法知道地址是否是从公钥正确派生的。
    • 如果将交易发送到无效地址,将销毁发送的以太,使其永远无法访问。
    • 验证接收人地址是否有效的工作,应该在用户界面一层完成。
  • 交易的value和data
    • 交易的主要“有效负载”包含在两个字段中:value和data。交易可以同时有value和data,仅有value,仅有data,或者既没有value也没有data。所以四种组合都有效。
    • 仅有value的交易就是一笔以太的付款
    • 仅有data的交易一般是合约调用
    • 进行合约调用的同时,我们除了传输data,还可以发送以太,从而交易中同时包含data和value
    • 没有value也没有data的交易,只是浪费gas,但它是有效的
  • 向EOA或合约传递data
    • 当交易包含数据有效负载时,它很可能是发送到合约地址的,但它同样可以发送给EOA
    • 如果发送data给EOA,数据负载的解释取决于钱包
    • 如果发送数据负载给合约地址,EVM会解释为函数调用,从payload里解码出函数名称和参数,调用该函数并传入参数
    • 发送给合约的数据有效负载是32字节的十六进制序列化编码:--函数选择器:函数原型的Keccak256哈希的前4个字节。这允许EVM明确地识别将要调用的函数。--函数参数:根据EVM定义的各种基本类型的规则进行编码。
  • 特殊交易:创建(部署)合约
    • 有一种特殊交易,具有数据负载且没有value,那就是一个创建新合约的交易。
    • 合约创建交易被发送到特殊目的地地址,即零地址0x0。该地址既不代表EOA也不代表合约。它永远不会花费以太或发起交易,它仅用作目的地,具有特殊含义“创建合约”。
    • 虽然零地址仅用于合约注册,但它有时会收到来自各种地址的付款。这种情况要么是偶然误操作,导致失去以太;要么是故意销毁以太。
    • 合约注册交易不应包含以太值,只包含合约的已编译字节码的数据有效负载。此交易的唯一效果是注册合约。

EVM

  1. 以太坊虚拟机
    • 以太坊虚拟机EVM是智能合约的运行环境
    • 作为区块验证协议的一部分,参与网络的每个节点都会运行EVM。他们会检查正在验证的块中列出的交易,并运行由EVM中交易触发的代码
    • EVM不仅是沙盒封装的,而且是完全隔离的,也就是说在EVM中运行的代码是无法访问网络、文件系统和其他进程的,甚至智能合约之间的访问也是受限的
    • 合约以字节码的格式存在于区块链上
    • 合约通常以高级语言solidity编写,通过EVM编译器编译为字节码,最终通过客户端部署到区块链网络中
  2. EVM和账户
    • 每个账户在EVM中都有一个键值对形式的持久化存储。其中key和value的长度都是256位,称之为存储空间。
  3. EVM和交易
    • 交易可以看作是从一个账户发送到另一个账户的消息,它可以包含二进制和以太币
    • 如果目标账户含有代码,此代码会在EVM中执行,并以payload作为入参,这就是合约的调用
    • 如果目标账户是零账户(账户地址为0),此交易就将创建一个新合约,这个用来创建合约的交易的payload会被转换为EVM字节码并执行,执行的输出作为合约代码永久存储
  4. EVM和gas
    • 合约被交易触发调用时,指令会在全网的每个节点上执行:这需要消耗算力成本;每一个指令的执行都有特定的消耗,gas就用来量化表示这个成本消耗
    • 一经创建,每笔交易都按照一定数量的gas预付一笔费用,目的是限制执行交易所需要的工作量和为交易支付手续费
    • EVM执行交易时,gas将按特定规则逐渐耗尽
    • gas price是交易发送者设置的一个值,作为发送者预付手续费的单价。如果交易执行后还有剩余,gas会原路返还
    • 无论执行到什么位置,一旦gas被耗尽(比如降为负值),将会触发一个out-of-gas异常。当前调用帧(call frame)所做的所有状态修改都将被回滚
  5. EVM数据存储Storage
    • 每个账户都有一块持久化的存储空间,称为storage,这是一个将256位字映射到256位字的key-value存储区,可以理解为合约的数据库
    • 永久存储在区块链中,由于会永久保存合约状态变量,所以读写的gas开销也最大

    Memory

    • 每一次消息调用,合约会临时获取一块干净的内存空间
    • 生命周期仅为整个方法执行期间,函数调用后回收,因为仅保存临时变量,故读写gas开销较小

    Stack

    • EVM不是基于寄存器的,而是基于栈的,因此所有的计算都在栈的区域执行
    • 存放部分局部值类型变量,几乎免费使用的内存,但有数量限制
  6. EVM指令集
    • 所有的指令都是针对“256位的字”这个基本的数据类型来进行操作
    • 具备常用的算数、位、逻辑和比较操作,也可以做到有条件和无条件跳转
    • 合约可以访问当前区块的相关属性,比如它的块高度和时间戳
  7. 消息调用
    • 合约可以通过消息调用的方式来调用其它合约或者发送以太币到非合约账户
    • 合约可以决定在其内部的消息调用中,对于剩余的gas,应发送和保留多少
    • 如果在内部消息调用时发生了out-of-gas异常(或其他任何异常),这将由一个被压入栈顶的错误值所指明;此时只有与该内部消息调用一起发送的gas会被消耗掉
  8. 委托调用
    • 一种特殊类型的消息调用
    • 目标地址的代码将在发起调用的合约的上下文中执行,并且msg.sender和msg.value不变
    • 可以由此实现“库”(library):可复用的代码库可以放在一个合约的存储上,通过委托调用引入相应代码
  9. 合约的创建和自毁
    • 通过一个特殊的消息调用create calls,合约可以创建其他合约
    • 合约代码从区块链上移除的唯一方式是合约在合约地址上的执行自毁操作selfdestruct;合约账户上剩余的以太币会发送给指定的目标,然后其存储和代码从状态中被移除

Leave a Reply

Your email address will not be published.