1.交易结构
1. Transaction structure
交易结构定义在 core/types/transaction.go 中:
The transaction structure is defined in core/types/transaction.go & #xff1a;
这个 atomic 是 go 语言的一个包 sync/atomic,用来实现原子操作。在这个结构体中, data 为数据字段,其余三个为缓存。下面是计算 hash 的函数:
This atomic is a package of sync/tomic, in the go language; it is used to perform atomic operations. In this structure xff0c; data is a data field xff0c; the remaining three are caches. Here is the function xff1a for the calculation of hash;
计算哈希前,首先会从缓存 tx.hash 中获取,如果取到,则直接返回值。没有,则使用
Calculates xff0c before Hashi; first takes ff0c from the cache tx.hash; if xff0c; returns the value directly. ff0c; uses xff0c;
rlpHash 计算:
rlpHash calculation & #xff1a;
hash 的计算方式为:先将交易的 tx.data 进行 rlpEncode 编码(定义在:core/types/transaction.go 中)
Hash is calculated as & #xff1a; tx.data for rlpEncode & #xff08; defined in #xff1a; core/types/transaction.go)
然后再进行算法为 Keccak256 的哈希计算。即:txhash=Keccak256(rlpEncode(tx.data))
Then the algorithm is xff1a; txhash= Keccak256 (rlpEncode (tx.data))
Transaction 中,data 为 txdata 类型的,定义于同文件中,里面详细规定了交易的具体字段:
Transaction & #xff0c; data is txdata type & #xff0c; defined in the same document & #xff0c; which details the specific field of the transaction & #xff1a;
这些字段的详细解释如下:
These fields are explained in detail below xff1a;
- AccountNonce:此交易的发送者已发送过的交易数(可防止重放攻击)
- Price:此交易的 gas price
- GasLimit:本交易允许消耗的最大 gas 数量
- Recipient:交易的接收者地址,如果这个字段为 nil 的话,则这个交易为“合约创建”类型交易
- Amount:交易转移的以太币数量,单位是 wei
- Payload:交易可以携带的数据,在不同类型的交易中有不同的含义
- V R S:交易的签名数据
我们会发现,交易中没有包含发送者地址这条数据,这是因为这个地址已包含在签名信息中,后面我们会分析到相关代码,另外,以太坊节点还会提供 JSON RPC 服务,供外部调用来传输数据。传输的数据格式为 json,因此,本文件中,还定义了交易的 json 类型数据结构,以及相关的转换函数。
We will find xff0c; the transaction does not contain the sender address xff0c; this is because this address is already included in the signature information xff0c; then we will analyse the code and xff0c; JSON RPC services xff0c; it will also be available for external transmission of data. The data transmitted is in the form of jsonxff0c; xff0c; in this document xff0c; and the json type data structure of the transaction is also defined xff0c; and the related conversion function.
函数为:MarshalJSON()和 UnmarshlJSON(),这两个函数会调用core/types/gen_tx_json.go 文件中的同名函数进行内外部数据类型的转换。
The functions are xff1a; MarshallJSON() and UnmarshlJSON(), both functions call for internal and external data type conversion using the same function in the core/types/gen_tx_json.go file.
2.交易存储
2. Transaction storage
交易的获取与存储函数为:Get/WriteTXLookupEntries ,定义在 core/database_util.go
The acquisition and storage function of the transaction is xff1a; Get/ WriteTXLookupEntries & #xff0c; defined in core/database_util.go
中。
Here we go.
对于每个传入的区块,该函数会读取块中的每一条交易来分别处理。首先建立条目(entry),数据类型为:txLookupEntry。内容包括区块哈希、区块号以及交易索引(交易 在区块中的位置),然后将此 entry 进行 rlp 编码作为存入数据库的 value。key 部分与区块存储类似,组成结构为交易前缀+交易哈希。
For each incoming block & #xff0c; this function will read each transaction in the block to process separately. First, create entry & #xff08;entry) , data type : txLookupEntry. This includes block Hashi, block number and transaction index #xff08; place of the transaction in the block xff09; xff0c; then encode this entry as value for the database.
此函数的调用主要在 core/blockchain.go 中,比如 WriteBlockAndState()会将区块写入数据库,处理 body 部分时需要分别处理每条交易。而 WriteBlockAndState 是在miner/worker.go 中 wait 函数调用的。mainer/worker.go 中 newWorker 函数在创建新矿工时,会调用 worker.wait().
This function is called mainly in core/ blockchain.go & #xff0c; e. g. WriterBlockAnd State( & #xff09; will write blocks into a database & #xff0c; will handle body parts of the body need to handle each transaction separately. WriteBlockAnd State is called for the wait function in Miner/ worker.go. NewWorker function in mainer/ worker.go is used when creating a new miner & #xff0c; will call worker.wait().
3.交易类型
3. Type of transaction
在源码中交易只有一种数据结构,如果非要给交易分个类的话,我认为交易可以分为三种:转账的交易、创建合约的交易、执行合约的交易。web3.js 提供了发送交易的接口:
There is only one data structure in the source code xff0c; xff0c if it is not necessary to classify the transaction; I think the transaction can be divided into three categories xff1a; transfer transactions, contract creation transactions, contract execution transactions. web3.js provides the interface for sending the transaction xff1a;
web3.eth.sendTransaction(transactionObject [, callback]) (web3.js 在internal/jsre/deps 中)
& #xff08; web3.js in internal/jsre/deps & #xff09;
参数是一个对象,如果在发送交易的时候指定不同的字段,区块链节点就可以识别出对应类型的交易。
Parameter is an object & #xff0c; the corresponding type of transaction can be identified if different fields & #xff0c are specified at the time the transaction is sent.
转账交易:
transfers and #xff1a;
? 转账是最简单的一种交易,这里转账是指从一个账户向另一个账户发送以太币。发送转账交易的时候只需要指定交易的发送者、接收者、转币的数量。使用 web3.js 发送转账交易应该像这样:
Transfers are the simplest type of transaction xff0c; here transfers are sent from one account to another in too much currency. All you need to do is specify the number of senders, receivers, trans-currencys of the transaction. Use web3.js to send transfer transactions like xff1a;
value 是转移的以太币数量,单位是 wei,对应的是源码中的 Amount 字段。to 对应的是源码中的 Recipient
Value is the transferred number in & #xff0c; in wi& #xff0c; corresponding to the Amount field in the source code. To corresponds to Recipient in the source code
创建合约交易:
Creates a contract transaction and #xff1a;
? 创建合约指的是将合约部署到区块链上,这也是通过发送交易来实现。在创建合约的交易中,to 字段要留空不填,在 data 字段中指定合约的二进制代码,from 字段是交易的发送者也是合约的创建者。
The creation of the contract refers to the deployment of the contract to the block chain xff0c; this is also done by sending a transaction. In the transaction that created the contract xff0c; to leave the field blank xff0c; in the data field the binary code of the contract xff0c; the From field is the sender of the transaction and the creator of the contract.
data 字段对应的是源码中的 Payload 字段。
Data field corresponds to the Payload field in the source code.
执行合约交易:
executes contractual transactions and #xff1a;
调用合约中的方法,需要将交易的 to 字段指定为要调用的合约的地址,通过 data 字段指定要调用的方法以及向该方法传递的参数。
Calls the method in the contract & #xff0c; the to-trading field is required to be designated as the address of the contract to be called & #xff0c; specifies the method to be called through the data field and the parameters to be passed on to this method.
data 字段需要特殊的编码规则,具体细节可以参考?Ethereum Contract ABI(自己拼接字段既不方便又容易出错,所以一般都使用封装好的 SDK(比如 web3.js) 来调用合约)。
Data fields require special coding rules & #xff0c; details can be consulted? Etheeum Contracting ABI( self-scrypted fields are neither easy nor easy to make mistakes xff0c; therefore, sealed SDK( e.g. web3.js) call contracts #xff09;
4.交易执行
4.
? 按照以太坊架构设计,交易的执行可大致分为内外两层结构:第一层是虚拟机外,包括执行前将 Transaction 类型转化成 Message,创建虚拟机(EVM)对象,计算一些 Gas 消耗,以及执行交易完毕后创建收据(Receipt)对象并返回等;第二层是虚拟机内,包括执行 转帐,和创建合约并执行合约的指令数组。
#xff0c based on the design of the EP architecture & #xff0c; transaction execution can be broadly divided into internal and external two-storey structures & #xff1a; first level is virtual & #xff0c; includes conversion of Transaction type to Message, prior to execution; creation of virtual machine (EVM) objects & #xff0c; calculation of some Gas consumption & #xff0c; and creation of receipt (Receipt) objects and return to #xff1b after the transaction is completed; second level is virtual #xff0c; includes execution of transfer #xff0c; and creation of contracts and enforcement of contract command arrays.
虚拟机外:
Virtual exterior and #xff1a;
执行 tx 的入口函数是 Process()函数,在 core/state_processor.go 中。
The entry function for executing tx is the Process() function & #xff0c; in core/ state_processor.go.
? Process()函数的核心是一个 for 循环,它将 Block 里的所有 tx 逐个遍历执行。具体的执行函数为同个 go 文件中的 ApplyTransaction()函数,它每次执行 tx, 会返回一个收据(Receipt)对象。Receipt 结构体的声明如下(core/types/receipt.go):
The core of the Process() function is a for cycle & #xff0c; it executes all tx in Block one by one. The specific execution function is the AppleTransation() function & #xff0c in the same go file; it returns a receipt object every time it executes tx. The statement of the Receipt structure is as follows: #xff08; core/ types/receipt.go) #xff1a;
? Receipt 中有一个 Log 类型的数组,其中每一个 Log 对象记录了 Tx 中一小步的操作。所以,每一个 tx 的执行结果,由一个 Receipt 对象来表示;更详细的内容,由一组 Log 对象来记录。这个 Log 数组很重要,比如在不同 Ethereum 节点(Node)的相互同步过程中, 待同步区块的 Log 数组有助于验证同步中收到的 block 是否正确和完整,所以会被单独同步(传输)。
#Receipt has a Log-type array & #xff0c; each Log object records a small step in Tx. So & #xff0c; each tx execution & #xff0c; a Receipt object shows & #xff1b; more detailed content & #xff0c; recorded by a group of Log objects. This Log array is important xff0c; for example, during synchronization of different Etheium nodes (Node) xff0c; the Log group of blocks to be synchronized helps verify the correctness and completeness of block received in synchronization #xff0c; so it is synced separately (transmission).
Receipt 的 PostState 保存了创建该 Receipt 对象时,整个 Block 内所有“帐户”的当时状态。Ethereum 里用 stateObject 来表示一个账户 Account,这个账户可转帐(transfer value), 可执行 tx, 它的唯一标示符是一个 Address 类型变量。 这个 Receipt.PostState 就是当时所在 Block 里所有 stateObject 对象的 RLP Hash 值。
PostState of Receipt saves the current state of all accounts in Block when creating the Receipt object. This account is available for transfer (transfer value) and the only identifier is an Address type variable. This Receipt. PostState is the RLP Hash value for all stateObject objects in Block at the time.
Bloom 类型是一个 Ethereum 内部实现的一个 256bit 长 Bloom Filter。 Bloom Filter 概念定义可见?wikipedia,http://blog.csdn.net/jiaomeng/article/details/1495500 它可用来快速验证一个新收到的对象是否处于一个已知的大量对象集合之中。这里 Receipt 的 Bloom, 被用以验证某个给定的 Log 是否处于 Receipt 已有的 Log 数组中。
The Bloom type is a 256bit long Bloom Field internally achieved by Etheum. The Bloom Filter concept definition is available? , can be used to quickly verify whether a newly received object is in a well-known mass of objects. Here, Receipt'sd=http://blog.csdn.net/jiaomng/details/149500" http://blog.csdn.net/jiaomeng/article/detals/1495500 >.
? 我们来看下 StateProcessor.ApplyTransaction()的具体实现,它的基本流程如下图:
♪ Let's look at StateProcessor.ApplyTransation(); its basic flow is as follows: #xff0c; #xff1a;
? ApplyTransaction()首先根据输入参数分别封装出一个 Message 对象和一个 EVM 对象,然后加上一个传入的 GasPool 类型变量,执行 core/state_transition.go 中的ApplyMessage(),而这个函数又调用同 go 文件中 TransitionDb()函数完成 tx 的执行,待TransitionDb()返回之后,创建一个收据 Receipt 对象,最后返回该 Recetip 对象,以及整个tx 执行过程所消耗 Gas 数量。
#ApplyTransation() first encapsulates a Message object and an EVM object & #xff0c according to the input parameters; then adds an incoming GasPool type variable & #xff0c; executes the AppleMessage() , in core/ state_translation.go; and then calls for the tx execution #xff0c; after the xDb() returns; creates a receipt Receipt object & #xff0c; finally returns the Recetip object #xff0c; and the entire tx execution process consumes Gas quantities.
GasPool 对象是在一个 Block 执行开始时创建,并在该 Block 内所有 tx 的执行过程中共享,对于一个 tx 的执行可视为“全局”存储对象; Message 由此次待执行的 tx 对象转化而来,并携带了解析出的 tx 的(转帐)转出方地址,属于待处理的数据对象;EVM 作为Ethereum 世界里的虚拟机(Virtual Machine),作为此次 tx 的实际执行者,完成转帐和合约(Contract)的相关操作。
GasPool is for the creation of xff0c at the start of a Block execution; and sharing xff0c in the execution of all tx in the Block; execution of a tx can be considered as a "global" storage object xff1b; Message from this pending tx object to xff0c; and carrying a transfer (transfer) address for the tx analysis xff0c; belonging to the subject of the data to be processed xff1b; EVM as the virtual machine (Virtual Machine) xff0c; as the actual implementer of this tx #xff0c; completing operations related to the transfer and contract (Contract).
我们来细看下 TransitioinDb()的执行过程(/core/state_transition.go)。假设有StateTransition 对象 st, 其成员变量 initialGas 表示初始可用 Gas 数量,gas 表示即时可用Gas 数量,初始值均为 0,于是 st.TransitionDb() 可由以下步骤展开:
Let's take a closer look at the implementation of TransitioinDb() (/core/state_translation.go). Assuming there's StateTransion object st, its member variable's intiial Gas indicates the number of xff0cs available initially; Gas indicates the number of xff0cs available immediately; the initial values are 0xff0c; then st.TranslationDb() can be started by xff1a;
首先执行 preCheck()函数,检查:1.交易中的 nonce 和账户 nonce 是否为同一个。2. 检查 gas 值是否合适(<=64 )
Execute the preCheck() function & #xff0c; check & #xff1a; 1. Whether the nonce in the transaction and the account are the same. 2. Check the Gas value for xff08; < & #61; 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 消耗量作文章。
By xff0c above; executing the EVM function xff0c in step 3; each step is written around Gas consumption.
步骤 5 的偿退机制很有意思,设立它的目的何在?目前为止我只能理解它可以避免交易执行过程中过快消耗 Gas,至于对其全面准确的理解尚需时日。
Step 5 is an interesting reimbursement mechanism xff0c; the purpose for which it was established is xff1f; so far I can only understand that it can avoid excessive consumption of Gas, in the course of transaction execution; and it will take time to understand it fully and accurately.
步骤 6 是奖励机制,没什么好说的。
Step 6 is the reward mechanism #xff0c; there's nothing to say.
Ethereum 中每个交易(transaction,tx)对象在被放进 block 时,都是经过数字签名的, 这样可以在后续传输和处理中随时验证 tx 是否经过篡改。Ethereum 采用的数字签名是椭圆曲线数字签名算法(Elliptic Cure Digital Signature Algorithm,ECDSA)。ECDSA 相比于基于大质数分解的 RSA 数字签名算法,可以在提供相同安全级别(in bits)的同时,仅需更短的公钥(public key)。这里需要特别留意的是,tx 的转帐转出方(发送方)地址,就是对该 tx 对象作 ECDSA 签名计算时所用的公钥 publicKey。
Each transaction in Etherum (transaction & #xff0c; tx) is , when placed in block; is digitally signed & #xff0c; this allows tx to be verified at any time during subsequent transmission and processing. Etheeum uses digital signatures as an elliptical digital signature algorithm (Elliptic Cure Digital Signature Algorithm, ). ECDSA compares to RSA digital Algorithm, which is a digital signature algorithm based on a high-quality breakdown #xff0; it can be transferred to the same security level (in bits) & #xff0; & & & kffxSpdkt.
Ethereum 中的数字签名计算过程所生成的签名(signature), 是一个长度为 65bytes 的字节数组,它被截成三段放进 tx 中,前 32bytes 赋值给成员变量 R, 再 32bytes 赋值给 S,1byte 赋给 V,当然由于 R、S、V 声明的类型都是*big.Int, 上述赋值存在[]byte –> big.Int 的类型转换。
The signature (signure) generated by the digital signature calculation process in Etheium is a byte array & #xff0c with a length of 65bytes; it is cut into three tx & #xff0c; former 32bytes assign value to member variable R and 32bytes assign value to S, 1 byte to V& #xff0c; and of course because the types of R, S, V declarations are *big. Int, the assigned value [] exists byte-& > big. int.
当需要恢复出 tx 对象的转帐转出方地址时(比如在需要执行该交易时),Ethereum 会先从 tx 的 signature 中恢复出公钥,再将公钥转化成一个 common.Address 类型的地址,signature 由 tx 对象的三个成员变量 R,S,V 转化成字节数组[]byte 后拼接得到。
& #xff0c; Etheium first recovers the public key from the tx signature & #xff0c; then converts the public key to a common.Address type address & #xff0c; signature from three member variables of the tx object R,S,V
Ethereum 对此定义了一个接口 Signer, 用来执行挂载签名,恢复公钥,对 tx 对象做哈希等操作。 接口定义是在:/ core/types/transaction_signing.go 的:
Etherum defined an interface signer to perform mount signature xff0c; restore public key xff0c; do tx objects etc. The interface is defined as xff1a;/ core/ typepes/transaction_signing.go xff1a;
这个接口主要做的就是恢复发送地址、生成签名格式、生成交易哈希、验证等。
This interface is primarily designed to restore the sending address, generate the signature format, generate the transaction Hashi, verify, etc.
生成数字签名的函数叫 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。
The function for generating digital signatures is SignTx(), the root cause is defined in core/types/transaction_signing.go( mobile/accounts.go also has SignTx, but this function is called on accounts/keystore/keystore.go; eventually also refers to types.SignTx) #xff0c; it first calls its function to generate signature and then calls the member variable R.S.V. to assign value to tx.
? Signer 接口中,恢复(提取?)转出方地址的函数为:Sender,Sender returns the address derived from the signature (V, R, S) using secp256k1。使用到的参数是:Signer 和 Transaction ,该函数定义在core/types/transaction_signing.go 中
#xff0c in Signer interface; restoring ( extracting & #xff1f; & #xff09; the function of the outgoing address is : Sender, Sender returns the address deactivated from the signature (V, R, S) using secp256k1. The parameters used are : Signer and Transaction & #xff0c; this function is defined in code/types/transaction_signing.go
? Sender()函数体中,signer.Sender()会从本次数字签名的签名字符串(signature)中恢复出公钥,并转化为 tx 的(转帐)转出方地址。此函数最终会调用同文件下的 recoverPlain 函数来进行恢复
? in the Sender() function & #xff0c; signer. Sender() restores the public key & #xff0c from the signature string (signature) of this digital signature; and converts it to the tx transfer address. This function ultimately calls the recoverPlain function under the same file for recovery
在上文提到的 ApplyTransaction()实现中,Transaction 对象需要首先被转化成 Message接口,用到的AsMessage()函数即调用了此处的 Sender()。调用路径为: AsMessage->transaction_signing.Sender(两个参数的)–>sender(单个参数的) 在 Transaction 对象 tx 的转帐转出方地址被解析出以后,tx 就被完全转换成了Message 类型,可以提供给虚拟机 EVM 执行了。
The ApplyTransaction() realization referred to above & #xff0c; Transaction objects need to be first converted to the Message interface & #xff0c; the AsMessage() function used is called here Sender(). The call path is : AsMessage-> Transport_signing.Sender(two arguments)—> Sender( xff09 for individual parameters; xff0; xff0c; tx has been fully converted to the Message type #xff0c; implemented by the Virtual EVM.
虚拟机内:
Virtual machine > #xff1a;
?
? 每个交易(Transaction)带有两部分内容(参数)需要执行:
• Each transaction (Transaction) has two parts xff08; parameter xff09; needs to be executed xff1a;
- 转帐,由转出方地址向转入方地址转帐一笔以太币 Ether;
- 携带的[]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)。
, of which the Context structure contains information on Transaction (GasPrice, GasLimit) & #xff0c; Block information (Number, Difficulty) & #xff0c; and transfer functions & #xff0c; provided to EVM; StateDB interface is a local behavioral interface designed for state. StateDB structures & #xff0c; relevant operation of statedb is available for EVM & #xff1b; Interpreter structure as interpreter & #xff0c; command (Code) to interpret the execution of the EVM contract (Contract).
? 注意,EVM 中定义的成员变量 Context 和 StateDB, 仅仅声明了变量名而无类型,而变量名同时又是其类型名,在 Golang 中,这种方式意味着宗主结构体可以直接调用该成员变量的所有方法和成员变量,比如 EVM 调用 Context 中的 Transfer()。
#xff0c; the membership variables defined in EVM Context and StateDB, which merely state the variable name without type & #xff0c; the variable name is also its type & #xff0c; in Golang & #xff0c; this means that the parent structure can call directly all the methods and membership variables of that member #xff0c; e.g. the EVM calls Transfer() in Context.
交易的转帐操作由 Context 对象中的 TransferFunc 类型函数来实现,类似的函数类型,还有 CanTransferFunc, 和 GetHashFunc。这三个类型的函数变量 CanTransfer, Transfer, GetHash,在 Context 初始化时从外部传入,目前使用的均是一个本地实现。可见目前的转帐函数 Transfer()的逻辑非常简单,转帐的转出账户减掉一笔以太币,转入账户加上一笔以太币。由于 EVM 调用的 Transfer()函数实现完全由 Context 提供,所以,假设如果基于 Ethereum 平台开发,需要设计一种全新的“转帐”模式,那么只需写一个新的 Transfer()函数实现,在 Context 初始化时赋值即可。
The transaction transfer operation is performed by TransferFunc type functions in Context objects to xff0c; similar function types xff0c; and CanTransferFunc, and GetHashFunc. The three types of function variables CanTransfer, Transfer, GetHash&xff0c; external transfer xff0c at the time of initialization of Context; all currently used is a local realization. See the logic of the current transfer function Transfer() is very simple xff0c; transfer accounts are reduced by xff0c; transfer accounts are added to the account in txff0c. Due to EVM transfer Transfer() functions are fully provided by xfff; thus #xff0; assumes that if the transfer function is based on the Ethereum platform #xff0c; design a brand new & tfffffnet is fully available.
有朋友或许会问,这里 Transfer()函数中对转出和转入账户的操作会立即生效么?万一两步操作之间有错误发生怎么办?答案是不会立即生效。StateDB 并不是真正的数据库, 只是一行为类似数据库的结构体。它在内部以 Trie 的数据结构来管理各个基于地址的账 户,可以理解成一个 cache;当该账户的信息有变化时,变化先存储在 Trie 中。仅当整个Block 要被插入到 BlockChain 时,StateDB 里缓存的所有账户的所有改动,才会被真正的提交到底层数据库。
A friend may ask xff0c; here the Transfer() function takes effect immediately for transfer and transfer operations xff1f; xff1f; the answer is not going to take effect immediately if there is an error between two steps. StateDB is not a real database xff0c; it is just the structure of a behavioral database. It manages all address-based accounts xff0c; understandably a cachexff1b; xff0c; changes are stored first in Trie when the information on the account changes. Only when the entire Block is to be inserted into Block Chain xff0c; all changes to all accounts stored in StateDB xff0c; are actually submitted to the bottom database.
合约的创建和赋值:
contract creation and award valuexff1a;
合约(Contract)是 EVM 用来执行(虚拟机)指令的结构体。Contract 的结构定义于:core/vm/contract.go 中,在这些成员变量里,caller 是转帐转出方地址(账户),self 是转入方地址,不过它们的类型都用接口 ContractRef 来表示;Code 是指令数组,其中每一个 byte 都对应于一个预定义的虚拟机指令;CodeHash 是 Code 的 RLP 哈希值;Input 是数据数组,是指令所操作的数据集合;Args 是参数。
The contract (Contract) is the structure used by EVM to execute (virtual machine) instructions. Contractor's structure is defined in : core/vm/contract.go & #xff0c; in these member variables xff0c; caller is the transferring address (account) xff0c; Self is the transferring address xff0c; however, their type is the interface ContractRef for xff1b; Code is the command array xff0c; each byte corresponds to a predefined virtual machine command xff1b; CodeHash is Code's RLP HaH value xff1b; Input is the data set xff0c; it is the data cluster operated by the directive ff1b; Args are parameters.
? 有意思的是 self 这个变量,为什么转入方地址要被命名成 self 呢? Contract 实现了ContractRef 接口,返回的恰恰就是这个 self 地址。
♪ Interestingly, Self & #xff0c; why did the transferring address have to be named Self & #xff1f; Contractor realized the ContractorRef interface & #xff0c; that's exactly the same self & address that's returned.
func (c *Contract) Address() common.Address { return c.self.Address()
}
? 所以当 Contract 对象作为一个 ContractRef 接口出现时,它返回的地址就是它的 self地址。那什么时候 Contract 会被类型转换成 ContractRef 呢?当 Contract A 调用另一个Contract B 时,A 就会作为 B 的 caller 成员变量出现。Contract 可以调用 Contract,这就为系统在业务上的潜在扩展,提供了空间。
♪ So when a Contractor object appears as a ContractorRef interface & #xff0c; the address it returns is its own. When the Contractor will be converted to a ContractorRef & #xff1f; when Contractor A calls another Contractor B & #xff0c; A will appear as a caller member variable for B. Contractor can call Contractor, this provides space for the potential expansion of the system #xff0c;
创建一个 Contract 对象时,重点关注对 self 的初始化,以及对 Code, CodeAddr 和Input 的赋值。
Creates a Contractor object & #xff0c; focuses on initialization of Self & #xff0c; and attribution values for Code, CodeAddr and Input.
另外,StateDB 提供方法 SetCode(),可以将指令数组 Code 存储在某个 stateObject 对象中; 方法 GetCode(),可以从某个 stateObject 对象中读取已有的指令数组 Code。
Also xff0c; StateDB provides the method SetCode(), can store command arrays in a stateObject object; method GetCode(), can read existing command arrays from a stateObject object.
? stateObject (core/state/state_object.go)是 Ethereum 里用来管理一个账户所有信息修改的结构体,它以一个 Address 类型变量为唯一标示符。StateDB 在内部用一个巨大的map 结构来管理这些 stateObject 对象。所有账户信息-包括 Ether 余额,指令数组 Code,该账户发起合约次数 nonce 等-它们发生的所有变化,会首先缓存到 StateDB 里的某个stateObject 里,然后在合适的时候,被 StateDB 一起提交到底层数据库。
? StateObject (core/state/state_object.go) is the structure used in Etherum to manage all information changes to an account & #xff0c; it uses an Address type variable as the only identifier. StateDB manages these stateObject objects internally with a huge Map structure. All account information - including Ether Balance & #xff0c; Command Group Code, the number of contracts initiated by the account nonce et al. - all changes that have occurred xff0c; will first be sequestered to a statusObject in StateDB & #xff0c; then #xff0c; be submitted to the base database together with StateDB.
? EVM(core/vm/evm.go)中 目前有五个函数可以创建并执行 Contract,按照作用和调用方式,可以分成两类:
♪ EVM( core/vm/evm.go) currently five functions can create and execute Contractor, by function and call xff0c; can be divided into two categories:
- ? Create(), Call(): 二者均在 StateProcessor 的 ApplyTransaction()被调用以执行单个交易,并且都有调用转帐函数完成转帐。
-
? CallCode(), DelegateCall(), StaticCall():三者由于分别对应于不同的虚拟机指令(1 byte)操作,不会用以执行单个交易,也都不能处理转帐。
CallCode(), DelegateCall(), StaticCall() & #xff1a; xff0c ; will not be used to execute a single transaction xff0c; and will not be able to process a transfer because each corresponds to a different virtual machine command (1 byte).
考虑到与执行交易的相关性,这里着重探讨 Create()和 Call()。先来看 Call(),它用来处理(转帐)转入方地址不为空的情况:
Taking into account the relevance to the execution of the transaction xff0c; focus here on Create() and Call(). First, look at Call()xff0c; it is used to process (transfer) transfers where the address is not empty xff1a;
Call()函数的逻辑可以简单分为以上 6 步。其中步骤(3)调用了转帐函数 Transfer(),转入账户 caller, 转出账户 addr;步骤(4)创建一个 Contract 对象,并初始化其成员变量 caller, self(addr), value 和 gas; 步骤(5)赋值 Contract 对象的 Code, CodeHash, CodeAddr 成员变量;步骤(6) 调用 run()函数执行该合约的指令,最后 Call()函数返回。相关代码可见:
The logic of the Call() function can be easily divided into the above six steps. The steps (3) refer to the transfer function Transfer(), transfer to the account caller, transfer account addr; step (4) create a Contracting object & #xff0c; and initialize its membership variable caller, self(addr), value and gas; step (5) assign value to the Contract object Code, CodeHash, CodeAddr member variable #xff1b; step (6) call the run() function to execute the contract command #xff0c; final Call() returns the relevant code #xff1a;
? 因为此时(转帐)转入地址不为空,所以直接将入参 addr 初始化 Contract 对象的 self 地址,并可从 StateDB 中(其实是以 addr 标识的账户 stateObject 对象)读取出相关的 Code 和CodeHash 并赋值给 contract 的成员变量。注意,此时转入方地址参数 addr 同时亦被赋值予 contract.CodeAddr。
♪ Because the transfer (transfer) is not empty at this time xff0c; the self address & #xff0c for the initialisation of the Contact object will be entered directly into the addr; and the relevant Code and CodeHash member variables will be given value to the contract. ff0c; at this point, the transfer address parameter Addr is also given value to the contact. CodeAddr.
再来看看 EVM.Create(),它用来处理(转帐)转入方地址为空的情况。
Then look at EVM.Create() & #xff0c; it is used to process (transfer) transfers to empty addresses.
与 Call()相比,Create()因为没有 Address 类型的入参 addr,其流程有几处明显不同:
& #xff0c compared to Call(); Create() due to lack of Address type entry addr, several distinct processes & #xff1a;
-
? 步骤(3)中创建一个新地址 contractAddr,作为(转帐)转入方地址,亦作为
• Create a new address in step (3) as (transfer) to the party & #xff0c; also as (transfer)
Contract 的 self 地址;
Self address for Contractor & #xff1b;
-
? 步骤(6)由于 contracrAddr 刚刚新建,db 中尚无与该地址相关的 Code 信息, 所以会将类型为[]byte 的入参 code,赋值予 Contract 对象的 Code 成员;
♪ Step (6) Due to the fact that confrAddr has just created xff0c; db does not have code information xff0c related to this address; therefore, entry code, of [ ]byte type; code member xff1b assigned value to Contractor objects;
-
? 步骤(8)将本次执行合约的返回结果,作为 contractAddr 所对应账户
♪ Step (8) returns the execution of the contract & #xff0c; as the corresponding account for the contractAddr
(stateObject 对象)的 Code 储存起来,以备下次调用。
Code storage xff0c; for next call.
? 还有一点隐藏的比较深,Call()有一个入参 input 类型为[]byte,而 Create()有一个入参code 类型同样为[]byte,没有入参 input,它们之间有无关系?其实,它们来源都是Transaction 对象 tx 的成员变量 Payload!调用 EVM.Create()或 Call()的入口在StateTransition.TransitionDb()中,当 tx.Recipent 为空时,tx.data.Payload 被当作所创建Contract 的 Code;当 tx.Recipient 不为空时,tx.data.Payload 被当作 Contract 的 Input。
• There is also a hidden depth & #xff0c; Call() has an input input type of []byte, while Create() has an entry code type of []byte, has not entered input, xff1f; actually xff0c; they are derived from the membership variable Payload! for Transaction objects; calls EVM.Create() or Call() at the entrance to StateTransion.TranslationDb() & #xff0c; when tx.Recipt is empty ff0c; tx.data. Payload is used as Code & #xff1b for creating Contract; when tx.RecictionD() is not available; xffta.int.
预编译合约
pre-programming contract
? EVM 中执行合约(指令)的函数是 run(),在 core/vm/evm.go 中其实现代码如下: 可见如果待执行的 Contract 对象恰好属于一组预编译的合约集合-此时以指令地址CodeAddr 为匹配项-那么它可以直接运行;没有经过预编译的 Contract,才会由Interpreter 解释执行。这里的”预编译”,可理解为不需要编译(解释)指令(Code)。预编译的合约,其逻辑全部固定且已知,所以执行中不再需要 Code,仅需 Input 即可。
The function for executing the contract (direction) in EVM is run() & #xff0c; its realization code in core/vm/evm.go is as follows & #xff1a; Visible if the object to be executed is properly part of a set of pre-compiled contracts - this time is matched by the command address CodeAddr - then it can run directly xff1b; there is no pre-complaint #xff0c; it will be explained by Interpreter. Here's pre-compilation & #xff0c; it can be understood as not required to compile (interpretation) instructions (Code). The pre-compiled contract #xff0c; its logic is all fixed and known xff0c; therefore, implementation is no longer needed #xff0c; only Input is available.
在代码实现中,预编译合约只需实现两个方法 Required()和 Run()即可,这两方法仅需一个入参 input。
xff0c in code realization; pre-compiling contracts only require two methods of required() and Run() xff0c; only one of these methods is required to enter input.
目前,Ethereuem 代码中已经加入了多个预编译合约,功能覆盖了包括椭圆曲线密钥恢复,SHA-3(256bits)哈希算法,RIPEMD-160 加密算法等等。相信基于自身业务的需求,二次开发者完全可以加入自己的预编译合约,大大加快合约的执行速度。
Currently xff0c; Etheeuem code has entered several pre-compiled contracts xff0c; functions cover including elliptical key restoration xff0c; SHA-3 (256bits) Hashi xff0c; RIPEMD-160 encryption algorithms, etc.
解释器执行合约的指令
interpreter executes the contract instructions
解释器 Interpreter 用来执行(非预编译的)合约指令。它的结构体 UML 关系图如下所示:
Interpreter is used to execute (non-pre-edited) contract instructions. Its structural UML relationship chart is xff1a as shown below;
? Interpreter 结构体通过一个 Config 类型的成员变量,间接持有一个包括 256 个operation 对象在内的数组 JumpTable。operation 是做什么的呢?
Interpreter structure via a Config-type membership variable & #xff0c; indirectly holding a array of 256 objects JumpTable. What does operation do #xff1f;
每个 operation 对象正对 应 一 个 已 定 义 的 虚 拟 机 指 令 , 它 所 含 有 的 四 个 函 数 变 量 execute, gasCost, validateStack, memorySize 提供了这个虚拟机指令所代表的所有操作。每个指令长度1byte,Contract 对象的成员变量 Code 类型为[]byte,就是这些虚拟机指令的任意集合,operation 对象的函数操作,主要会用到 Stack,Memory, IntPool 这几个自定义的数据结构。
Each operation object is providing all the actions that this virtual machine command represents. Each command length is 1 byte, Contract object member variable is [] byte#xff0c; this is the random collection of these virtual machine commands #xff0c; the function operation of the object xff0c; the main use will be Stack, Memory, IntPool, a few custom data structures.
? 这样一来,Interpreter 的 Run()函数就很好理解了,其核心流程就是逐个 byte 遍历入参 Contract 对象的 Code 变量,将其解释为一个已知的 operation,然后依次调用该operation 对象的四个函数,流程示意图如下:
♪ So that the Run() function of xff0c; Interpreter understands xff0c well; its core process is the Code variable xff0c that goes through the Contact object byte; interprets it as a known operation #xff0c; then calls the four functions of the application object xff0c; the flow chart is as follows #xff1a;
operation 在操作过程中,会需要几个数据结构: Stack,实现了标准容器 -栈的行为;Memory,一个字节数组,可表示线性排列的任意数据;还有一个 intPool,提供对big.Int 数据的存储和读取。
Operation & #xff0c during operation; several data structures & #xff1a; Stack, behaviour & #xff1b; Memory, a byte array & #xff0c; any data that can be presented as linear; and an intPool, providing storage and reading of Big.Int data.
已定义的 operation,种类很丰富,包括:
Defined operations & #xff0c; very diverse xff0c; including xff1a;
- ? 算术运算:ADD,MUL,SUB,DIV,SDIV,MOD,SMOD,EXP…;
- ? 逻辑运算:LT,GT,EQ,ISZERO,AND,XOR,OR,NOT…;
-
? 业务功能:SHA3,ADDRESS,BALANCE,ORIGIN,CALLER,GASPRICE,LOG1,LOG2…等等
• Business functions xff1a; SHA3, ADDRRESS, BALANCE, ORIGIN, CALLER, GASPRICE, LEG1xff0c; LOG2... and so on
需要特别注意的是 LOGn 指令操作,它用来创建 n 个 Log 对象,这里 n 最大是 4。还记得 Log 在何时被用到么?每个交易(Transaction,tx)执行完成后,会创建一个 Receipt 对象用来记录这个交易的执行结果。Receipt 携带一个 Log 数组,用来记录 tx 操作过程中的所有变动细节,而这些 Log,正是通过合适的 LOGn 指令-即合约指令数组(Contract.Code) 中的单个 byte,在其对应的 operation 里被创建出来的。每个新创建的 Log 对象被缓存在StateDB 中的相对应的 stateObject 里,待需要时从 StateDB 中读取。
Of particular note is the LOGn command operation xff0c; it is used to create n Log objects , here n the maximum is 4. Remember when Log was used xff1f; xff0c after the completion of each transaction (Transaction, tx); created a Receipt object to record the results of the transaction. Receipt carries a Log array xff0c; used to record all the details of changes in the tx operation xff0c; these Logxff0c; individual byte#xff0c in the appropriate LOGN command-i-contract command set (Contract. Code); created in its counterpart application.
注册有任何问题请添加 微信:MVIP619 拉你进入群
打开微信扫一扫
添加客服
进入交流群
发表评论