一、 交易的结构

1. Transaction结构

交易结构定义在 core/types/transaction.go 中:

type Transaction struct {//交易数据data txdata// cacheshash atomic.Valuesize atomic.Value//钱包根据 from来找到 //account := accounts.Account{Address: args.From}from atomic.Value}

(1)data

data 为数据字段(txdata 类型),其余三个为缓存。

(2)hash

其中 atomic 是 go 语言的一个包 sync/atomic,用来实现原子操作。

下面是计算hash的函数

计算哈希前,首先会从缓存 tx.hash 中获取,如果取到,则直接返回值。没有,则使用rlpHash 计算:

hash 的计算方式为:先将交易的 tx.data 进行 rlpEncode 编码(定义在:core/types/transaction.go 中)

然后再进行算法为 Keccak256 的哈希计算。即:txhash=Keccak256(rlpEncode(tx.data))

2. txdata结构

type txdata struct {//发送者发起的交易总数AccountNonce uint64          `json:"nonce"    gencodec:"required"`//交易的Gas价格Price        *big.Int        `json:"gasPrice" gencodec:"required"`//交易允许消耗的最大GasGasLimit     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.//交易HAshHash *common.Hash `json:"hash" rlp:"-"`
}
  • nonce: 交易发起者填写的序列号,代表此交易的发送者已发送过的交易数(可防止重放攻击)
  • Gasprice: 交易者愿意支付的Gas价格
  • Startgas:交易者愿意花费的最大Gas数量,本交易允许消耗的最大 gas 数量
  • To: 交易接收方地址(20Bytes),如果这个字段为 nil 的话,则这个交易为“合约创建”类型交易
  • Value: 交易转移的以太币数量,单位是 wei
  • startgas*gasprice = 最终ether数值
  • Data:交易可以携带的数据,可变长度的二进制数据载荷,在不同类型的交易中有不同的含义
  • V,R,S: 交易的签名数据,ECDSA数字签名信息

3. 交易中没有包含发送者地址这条数据

这是因为这个地址已包含在签名信息中。

二、交易的类型

在源码中交易只有一种数据结构,如果非要给交易分个类的话,我认为交易可以分为三种:

  • 转账的交易、
  • 创建合约的交易、
  • 执行合约的交易。

1. 发送交易的接口

web3.js 提供了发送交易的接口:

web3.eth.sendTransaction(transactionObject [, callback]) (web3.js 在internal/jsre/deps 中)

参数是一个对象,如果在发送交易的时候指定不同的字段,区块链节点就可以识别出对应类型的交易。

2. 转账交易

​ 转账是最简单的一种交易,这里转账是指从一个账户向另一个账户发送以太币。

发送转账交易的时候只需要指定交易的发送者、接收者、转币的数量。

使用 web3.js 发送转账交易应该像这样:

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

3. 创建合约交易

​ 创建合约指的是将合约部署到区块链上,这也是通过发送交易来实现。

在创建合约的交易中,to 字段要留空不填,在 data 字段中指定合约的二进制代码,from 字段是交易的发送者也是合约的创建者。

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

4. 执行合约交易

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

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

实际上,一段智能合约是被唯一的(合约)地址所标识,该地址有自己的资金余额(以太币),并且一经检索到有一笔交易发送至该(合约)地址,以太坊网络节点就会执行合约逻辑。以太坊自带执行引擎,即以太坊虚拟机(EVM)来执行智能合约。

三、 创建交易

1. 创建交易时要提交的数据

代码 internal/ethapi/api.go

// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
type SendTxArgs struct {From     common.Address  `json:"from"`To       *common.Address `json:"to"`Gas      *hexutil.Uint64 `json:"gas"`GasPrice *hexutil.Big    `json:"gasPrice"`Value    *hexutil.Big    `json:"value"`Nonce    *hexutil.Uint64 `json:"nonce"`// We accept "data" and "input" for backwards-compatibility reasons. "input" is the// newer name and should be preferred by clients.Data  *hexutil.Bytes `json:"data"`Input *hexutil.Bytes `json:"input"`
}

2. 交易的 value 和 data

  • 交易的主要“有效负载”包含在两个字段中:value 和 data。交易可以同时有 value 和 data,仅有 value,仅有 data,或者既没有 value 也没有 data。所有四种组合都有效。
  • 仅有 value 的交易就是一笔以太的付款
  • 仅有 data 的交易一般是合约调用
  • 进行合约调用的同时,我们除了传输 data, 还可以发送以太,从而交易中同时包含 data 和 value
  • 没有 value 也没有 data 的交易,只是在浪费 gas,但它是有效的

注意:以太坊单笔交易运行携带最大数据量为44KB

四、交易执行

1. 以太坊架构设计

按照以太坊架构设计,交易的执行可大致分为内外两层结构:

  • 第一层是虚拟机外,包括执行前将 Transaction 类型转化成 Message,创建虚拟机(EVM)对象,计算一些 Gas 消耗,以及执行交易完毕后创建收据(Receipt)对象并返回等;
  • 第二层是虚拟机内,包括执行 转帐,和创建合约并执行合约的指令数组。

2. 虚拟机外

执行 tx 的入口函数是 Process()函数,在 core/state_processor.go 中。

​ Process()函数的核心是一个 for 循环,它将 Block 里的所有 tx 逐个遍历执行。

具体的执行函数为同个 go 文件中的 ApplyTransaction()函数,它每次执行 tx, 会返回一个收据(Receipt)对象。

Receipt 结构体的声明如下(core/types/receipt.go):

​ Receipt 中有一个 Log 类型的数组,其中每一个 Log 对象记录了 Tx 中一小步的操作。

所以,每一个 tx 的执行结果,由一个 Receipt 对象来表示;更详细的内容,由一组 Log 对象来记录。

这个 Log 数组很重要,比如在不同 Ethereum 节点(Node)的相互同步过程中, 待同步区块的 Log 数组有助于验证同步中收到的 block 是否正确和完整,所以会被单独同步(传输)。

Receipt 的 PostState 保存了创建该 Receipt 对象时,整个 Block 内所有“帐户”的当时状态。Ethereum 里用 stateObject 来表示一个账户 Account,这个账户可转帐(transfer value), 可执行 tx, 它的唯一标示符是一个 Address 类型变量。

这个 Receipt.PostState 就是当时所在 Block 里所有 stateObject 对象的 RLP Hash 值。

Bloom 类型是一个 Ethereum 内部实现的一个 256bit 长 Bloom Filter。 Bloom Filter 概念定义可见 wikipedia,http://blog.csdn.net/jiaomeng/article/details/1495500 它可用来快速验证一个新收到的对象是否处于一个已知的大量对象集合之中。这里 Receipt 的 Bloom, 被用以验证某个给定的 Log 是否处于 Receipt 已有的 Log 数组中。

​ 我们来看下 StateProcessor.ApplyTransaction()的具体实现,它的基本流程如下图:

​ ApplyTransaction()首先根据输入参数分别封装出一个 Message 对象和一个 EVM 对象,然后加上一个传入的 GasPool 类型变量,执行 core/state_transition.go 中的ApplyMessage(),而这个函数又调用同 go 文件中 TransitionDb()函数完成 tx 的执行,待TransitionDb()返回之后,创建一个收据 Receipt 对象,最后返回该 Recetip 对象,以及整个tx 执行过程所消耗 Gas 数量。

GasPool 对象是在一个 Block 执行开始时创建,并在该 Block 内所有 tx 的执行过程中共享,对于一个 tx 的执行可视为“全局”存储对象;

Message 由此次待执行的 tx 对象转化而来,并携带了解析出的 tx 的(转帐)转出方地址,属于待处理的数据对象;

EVM 作为Ethereum 世界里的虚拟机(Virtual Machine),作为此次 tx 的实际执行者,完成转帐和合约(Contract)的相关操作。

我们来细看下 TransitioinDb()的执行过程(/core/state_transition.go)。假设有StateTransition 对象 st, 其成员变量 initialGas 表示初始可用 Gas 数量,gas 表示即时可用Gas 数量,初始值均为 0,于是 st.TransitionDb() 可由以下步骤展开:

首先执行 preCheck()函数,检查:

1.交易中的 nonce 和账户 nonce 是否为同一个。

2. 检查 gas 值是否合适(<=64 )

  • 购买 Gas。首先从交易的(转帐)转出方账户扣除一笔 Ether,费用等于tx.data.GasLimit * tx.data.Price; 同 时 st.initialGas = st.gas = tx.data.GasLimit; 然 后(GasPool) gp –= st.gas 。
  • 计算 tx 的固有 Gas 消耗 – intrinsicGas。它分为两个部分,每一个 tx 预设的消耗量,这个消耗量还因 tx 是否含有(转帐)转入方地址而略有不同;以及针对tx.data.Payload 的 Gas 消耗,Payload 类型是[]byte,关于它的固有消耗依赖于[]byte 中非 0 字节和 0 字节的长度。最终,st.gas –= intrinsicGas
  • EVM 执行。如果交易的(转帐)转入方地址(tx.data.Recipient)为空,即contractCreation,调用 EVM 的 Create()函数;否则,调用 Call()函数。无论哪个函数返回后,更新 st.gas。
  • 计算本次执行交易的实际 Gas 消耗: requiredGas = st.initialGas – st.gas
  • 偿退 Gas。它包括两个部分:首先将剩余 st.gas 折算成 Ether,归还给交易的(转帐)转出方账户;然后,基于实际消耗量 requiredGas,系统提供一定的补偿,数量为 refundGas。refundGas 所折算的 Ether 会被立即加在(转帐)转出方账户上, 同时 st.gas += refundGas,gp += st.gas,即剩余的 Gas 加上系统补偿的 Gas,被一起归并进 GasPool,供之后的交易执行使用。
  • 奖励所属区块的挖掘者:系统给所属区块的作者,亦即挖掘者账户,增加一笔金额,数额等于 st.data,Price * (st.initialGas – st.gas)。注意,这里的 st.gas 在步骤 5 中被加上了 refundGas, 所以这笔奖励金所对应的 Gas,其数量小于该交易实际消耗量 requiredGas。

由上可见,除了步骤 3 中 EVM 函数的执行,其他每个步骤都在围绕着 Gas 消耗量作文章。

步骤 5 的偿退机制很有意思,设立它的目的何在?目前为止我只能理解它可以避免交易执行过程中过快消耗 Gas,至于对其全面准确的理解尚需时日。

步骤 6 是奖励机制,没什么好说的。

Ethereum 中每个交易(transaction,tx)对象在被放进 block 时,都是经过数字签名的, 这样可以在后续传输和处理中随时验证 tx 是否经过篡改。

Ethereum 采用的数字签名是椭圆曲线数字签名算法(Elliptic Cure Digital Signature Algorithm,ECDSA)。

ECDSA 相比于基于大质数分解的 RSA 数字签名算法,可以在提供相同安全级别(in bits)的同时,仅需更短的公钥(public key)。

这里需要特别留意的是,tx 的转帐转出方(发送方)地址,就是对该 tx 对象作 ECDSA 签名计算时所用的公钥 publicKey。

Ethereum 中的数字签名计算过程所生成的签名(signature), 是一个长度为 65bytes 的字节数组,它被截成三段放进 tx 中,前 32bytes 赋值给成员变量 R, 再 32bytes 赋值给 S,1byte 赋给 V,当然由于 R、S、V 声明的类型都是*big.Int, 上述赋值存在[]byte –> big.Int 的类型转换。

当需要恢复出 tx 对象的转帐转出方地址时(比如在需要执行该交易时),Ethereum 会先从 tx 的 signature 中恢复出公钥,再将公钥转化成一个 common.Address 类型的地址,signature 由 tx 对象的三个成员变量 R,S,V 转化成字节数组[]byte 后拼接得到。

Ethereum 对此定义了一个接口 Signer, 用来执行挂载签名,恢复公钥,对 tx 对象做哈希等操作。

接口定义是在:/ core/types/transaction_signing.go 的:

这个接口主要做的就是恢复发送地址、生成签名格式、生成交易哈希、验证等。

生成数字签名的函数叫 SignTx(),最根源是定义在 core/types/transaction_signing.go(mobile/accounts.go 中也有 SignTx,但是这个函数是调用 accounts/keystore/keystore.go中的 SignTX,最终又调用 types.SignTx),它会先调用其函数生成 signature, 然后调用tx.WithSignature()将 signature 分段赋值给 tx 的成员变量 R,S,V。

​ Signer 接口中,恢复(提取?)转出方地址的函数为:Sender,Sender returns the address derived from the signature (V, R, S) using secp256k1。使用到的参数是:Signer 和 Transaction ,该函数定义在core/types/transaction_signing.go 中

​ Sender()函数体中,signer.Sender()会从本次数字签名的签名字符串(signature)中恢复出公钥,并转化为 tx 的(转帐)转出方地址。

此函数最终会调用同文件下的 recoverPlain 函数来进行恢复

在上文提到的 ApplyTransaction()实现中,Transaction 对象需要首先被转化成 Message接口,用到的AsMessage()函数即调用了此处的 Sender()。

调用路径为: AsMessage->transaction_signing.Sender(两个参数的)–>sender(单个参数的) 在 Transaction 对象 tx 的转帐转出方地址被解析出以后,tx 就被完全转换成了Message 类型,可以提供给虚拟机 EVM 执行了。

3. 虚拟机内:

​ 每个交易(Transaction)带有两部分内容(参数)需要执行:

  1. 转帐,由转出方地址向转入方地址转帐一笔以太币 Ether;
  2. 携带的[]byte 类型成员变量 Payload,其每一个 byte 都对应了一个单独虚拟机指令。这些内容都是由 EVM(Ethereum Virtual Machine)对象来完成 的。

EVM 结构体是 Ethereum 虚拟机机制的核心,它与协同类的 UML 关系图如下:

​ 其中 Context 结构体分别携带了 Transaction 的信息(GasPrice, GasLimit),Block 的信息(Number, Difficulty),以及转帐函数等,提供给 EVM;StateDB 接口是针对 state.StateDB 结构体设计的本地行为接口,可为 EVM 提供 statedb 的相关操作;

Interpreter 结构体作为解释器,用来解释执行 EVM 中合约(Contract)的指令(Code)。

​ 注意,EVM 中定义的成员变量 Context 和 StateDB, 仅仅声明了变量名而无类型,而变量名同时又是其类型名,在 Golang 中,这种方式意味着宗主结构体可以直接调用该成员变量的所有方法和成员变量,比如 EVM 调用 Context 中的 Transfer()。

交易的转帐操作由 Context 对象中的 TransferFunc 类型函数来实现,类似的函数类型,还有 CanTransferFunc, 和 GetHashFunc。

这三个类型的函数变量 CanTransfer, Transfer, GetHash,在 Context 初始化时从外部传入,目前使用的均是一个本地实现。

可见目前的转帐函数 Transfer()的逻辑非常简单,转帐的转出账户减掉一笔以太币,转入账户加上一笔以太币。由于 EVM 调用的 Transfer()函数实现完全由 Context 提供,所以,假设如果基于 Ethereum 平台开发,需要设计一种全新的“转帐”模式,那么只需写一个新的 Transfer()函数实现,在 Context 初始化时赋值即可。

有朋友或许会问,这里 Transfer()函数中对转出和转入账户的操作会立即生效么?万一两步操作之间有错误发生怎么办?答案是不会立即生效。

StateDB 并不是真正的数据库, 只是一行为类似数据库的结构体。它在内部以 Trie 的数据结构来管理各个基于地址的账 户,可以理解成一个 cache;

当该账户的信息有变化时,变化先存储在 Trie 中。仅当整个Block 要被插入到 BlockChain 时,StateDB 里缓存的所有账户的所有改动,才会被真正的提交到底层数据库。

五、交易存储

交易的获取与存储函数为:Get/WriteTXLookupEntries ,定义在 core/database_util.go中。

对于每个传入的区块,该函数会读取块中的每一条交易来分别处理。

  • 首先建立条目(entry),数据类型为:txLookupEntry。内容包括区块哈希、区块号以及交易索引(交易 在区块中的位置),
  • 然后将此 entry 进行 rlp 编码作为存入数据库的 value。key 部分与区块存储类似,组成结构为交易前缀+交易哈希。

此函数的调用主要在 core/blockchain.go 中,比如 WriteBlockAndState()会将区块写入数据库,处理 body 部分时需要分别处理每条交易。

而 WriteBlockAndState 是在miner/worker.go 中 wait 函数调用的。

mainer/worker.go 中 newWorker 函数在创建新矿工时,会调用 worker.wait().

内容来自

以太坊交易结构解析和负载Payload

https://www.cnblogs.com/wwei233/p/13476614.html

以太坊探究:ETH交易部分分析

https://m.8btc.com/article/265557

区块链 以太坊 交易结构、执行、存储 解析 交易中为什么没有包含发送者地址这条数据相关推荐

  1. 8支团队正在努力构建下一代区块链以太坊Ethereum 2.0

    2019独角兽企业重金招聘Python工程师标准>>> "我们不想在构建 Ethereum 2.0时重新造轮子." 谈到开发人员为 Ethereum 区块链进行两 ...

  2. 可编程区块链以太坊的未来

    区块链的火热就不用说了,回看一下Vinay Gupta 2015年发表的文章<内容可编程的区块链:以太坊的未来>,感受一下先行者的思想. 到本文结束时,你将了解一般的区块链(特别是下一代区 ...

  3. 回看2015年是如何描述区块链以太坊的未来

    区块链的火热就不用说了,回看一下Vinay Gupta 2015年发表的文章<内容可编程的区块链:以太坊的未来>,感受一下先行者的思想. 到本文结束时,你将了解一般的区块链(特别是下一代区 ...

  4. 回望Vinay2015年的文章:内容可编程的区块链——以太坊的未来

    2019独角兽企业重金招聘Python工程师标准>>> 区块链的火热就不用说了,回看一下Vinay Gupta 2015年发表的文章<内容可编程的区块链:以太坊的未来>, ...

  5. 上下文可编程区块链——以太坊的未来

    区块链的火热就不用说了,回看一下Vinay Gupta 2015年发表的文章<内容可编程的区块链:以太坊的未来>,感受一下先行者的思想. 到本文结束时,你将了解一般的区块链(特别是下一代区 ...

  6. 【区块链-以太坊】5 Ubuntu下truffle ganache安装及使用

    [区块链-以太坊]5 Ubuntu下truffle & ganache安装及使用 1 truffle安装 1)输入sudo npm install -g truffle 2)将truffle复 ...

  7. 区块链-以太坊学习资料汇总

    最近一段时间以来,对区块链的底层研究和基于区块链的应用开发已经越来越火热了.机缘巧合,目前我也在进行区块链方面的开发,在之后的博客中,我会和大家一起分享区块链开发中的酸甜苦辣.今天我先来对收藏区块链- ...

  8. 区块链 -- 以太坊的36个概念

    2019独角兽企业重金招聘Python工程师标准>>> 1.以太币(Ether)产生规则和比特币有哪些区别? 1. 出块时间. 比特币出块时间是平均10分钟,而以太坊约是15秒. 2 ...

  9. 区块链以太坊以及hyperledger总结

    https://learnblockchain.cn/ 1.什么是智能合约?它有什么特点? 就是具有交互能力而且能够在区块链中传递的合约 一个由计算机代码控制的以太币账户 特点: 公开透明.能即时与区 ...

最新文章

  1. iPhone开发应用Sqlite使用手册
  2. 如何实现一个优质的微服务框架:Apache ServiceComb 的开放性设计
  3. python调用.net动态库
  4. 学习一段深有感悟的讲话
  5. 努力一下,还是可以成为技术美术(TA)的
  6. vue 列表渲染 v-for
  7. ElasticSearch filter查询
  8. linux下java多线程_Linux系统下Java问题排查——cpu使用率过高或多线程锁问题
  9. 途家民宿4月26日后停止20城直营业务
  10. Mysql-5.6.x多实例配置
  11. JAVA里plain_Java中POJO及其细分XO、DAO的概念
  12. 2020年互联网大厂中秋礼盒PK!看看你的礼盒怎么样
  13. html ajax 图片上传,Ajax 上传图片并预览的简单实现
  14. 信号、频谱、能量、功率、噪声
  15. idea右侧没有maven,main方法无启动图标解决方法
  16. python+selenium框架跳过https安全隐私问题解决方案
  17. SM2 原文签名与HASH签名实现区别
  18. iOS----------适配iOS12
  19. Python numpy.meshgrid()
  20. 安徽基础教育资源应用平台.html,安徽省基础教育资源应用平台

热门文章

  1. 如何修改mtk6573的设备号
  2. js面向对象编程class
  3. JavaScript 五大主流浏览器内核及Js引擎介绍
  4. IOI APIO NOI NOIP 知名 选手 神犇 大牛 大神 博客
  5. uni-app与Html 标签的区别
  6. 时代的一粒沙,压在个人头上便是整整的一座山
  7. 深信服AD应用交付基础介绍
  8. 研发项目经理需要具备的7大核心素质特征
  9. SpringBoot项目配置disconf
  10. 关于Devstack网络问题。