以太坊原理分析4 交易

关于交易的思考

狭义的交易是从A到B的一笔转账。广义的交易,是区块链系统中的一个指令,一条有效的指令,会改变区块链中的账户状态。在设计交易指令的时候,需要考虑几个问题:
* 交易类型
* 发起者身份确认
* 接受者身份确认(也可不一定马上确认,比如比特币)
* 发起内容
* 交易代价(交易代价在普通交易中也就是交易费,交易代价的引入,完全是因为区块链系统的去中心化特性:要想有人收录你的交易,你就得给予利益)

以下描述的是以太坊中的交易,包括普通交易和对智能合约账户的指令。

转账交易流程

  • 用户输入转账的地址和转入的地址和转出的金额
  • 系统通过转出的地址的私钥对转账信息进行签名(用于证明这 笔交易确实有本人进行)
  • 系统对交易信息进行验证
  • 把这笔交易入到本地的txpool中(就是缓存交易池)
  • 把交易信息广播给其它节点

以太坊交易内容

  • to:接收者的地址。在合约创建交易中,合约账户的地址还没有存在,所以值先空着。
  • nonce:发送者发送交易数的计数。
  • gasPrice:发送者愿意支付执行交易所需的每个gas的Wei数量。(理论上gas的单位可以不是wei, 但实际上就是wei)
  • gasLimit:发送者愿意为执行交易支付gas数量的最大值。这个数量被设置之后在任何计算完成之前就会被提前扣掉。
  • value:从发送者转移到接收者的Wei数量。在合约创建交易中,value作为新建合约账户的开始余额。
  • v,r,s:用于产生标识交易发生着的签名。
  • init(只有在合约创建交易中存在):用来初始化新合约账户的EVM代码片段。init值会执行一次,然后就会被丢弃。当init第一次执行的时候,它返回一个账户代码体,也就是永久与合约账户关联的一段代码。
  • input(可选域,只有在消息通信中存在):消息通话中的输入数据(也就是智能合约调用时得参数)。例如,如果智能合约就是一个域名注册服务,那么调用合约可能就会期待输入域例如域名和IP地址。
    > 以太坊的结构里面没有from字段,但from信息其实保存在v,r,s里面

交易数据结构

core/types/transaction.go

type Transaction struct {
    data txdata
    // caches
    hash atomic.Value
    size atomic.Value
    from atomic.Value
}

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:"-"`
}

交易和消息

  • 交易是外部世界和以太坊内部状态的桥梁
  • 消息或内部交易类似于交易,不过与交易有着最大的不同点—它们不是由外部拥有账户产生的。相反,他们是被合约产生的。
  • 消息是虚拟对象,与交易不同,没有在链上,而且只存在与以太坊执行环境。
  • 内部交易或者消息不包含gasLimit

以太坊原理分析3 区块

区块结构

在以太坊中,一个区块包含:
* 区块头
* 关于包含在此区块中交易集的信息
* 与当前块的uncles相关的一系列其他区块头,叔块

uncles解释

uncle就是一个区块的父区块与当前区块父区块的父区块是相同的。也就是当前区块的叔块。
由于以太坊的构造,它的区块生产时间(大概15秒左右)比其他的区块链例如Bitcoin(大概10分钟左右)要快很多。这使得交易的处理更快。但是,更短的区块生产时间的一个缺点就是:更多的竞争区块会被矿工发现。这些竞争区块同样也被称为“孤区块”(也就是被挖出来但是不会被添加到主链上的区块)。

uncles的目的就是为了帮助奖励矿工纳入这些孤区块。矿工包含的uncles必须是有效的,也就是uncles必须在父区块的第6个子区块之内或更小范围内。在第6个子区块之后,陈旧的孤区块将不会再被引用(因为包含老旧的交易会使事情变得复杂一点)。

uncle区块会收到比全区块少一点的奖励。不管怎样,依然存在激励来让矿工们纳入孤区块并能从中获得一些报酬。

core/types/block.go
type Block struct {
    header       *Header
    uncles       []*Header
    transactions Transactions

    // caches
    hash atomic.Value
    size atomic.Value

    // Td is used by package core to store the total difficulty
    // of the chain up to and including the block.
    td *big.Int

    // These fields are used by package eth to track
    // inter-peer block relay.
    ReceivedAt   time.Time
    ReceivedFrom interface{}
}

区块头

区块头是一个区块的一部分,包含了:
* parentHash:父区块头的Hash值(这也是使得区块变成区块链的原因)
* UncleHash:当前区块Uncles列表的Hash值
* miner:接收挖此区块费用的账户地址
* stateRoot:状态树根节点的Hash值(回忆一下我们之前所说的保存在头中的状态树以及它使得轻客户端认证任何关于状态的事情都变得非常简单)
* transactionsRoot:包含此区块所列的所有交易的树的根节点Hash值
* receiptsRoot:包含此区块所列的所有交易收据的树的根节点Hash值
* logsBloom:由日志信息组成的一个Bloom过滤器 (数据结构)
* difficulty: 此区块的难度级别
* number:当前区块的计数(创世纪块的区块序号为0,对于每个后续区块,区块序号都增加1)
* gasLimit:每个区块的当前gas limit
* gasUsed: 此区块中交易所用的总gas量
* timestamp:此区块成立时的unix的时间戳
* extraData:与此区块相关的附加数据
* mixHash:一个Hash值,当与nonce组合时,证明此区块已经执行了足够的计算
* nonce:一个Hash值,当与mixHash组合时,证明此区块已经执行了足够的计算

core/types/block.go
// Header represents a block header in the Ethereum blockchain.
type Header struct {
    ParentHash  common.Hash    `json:"parentHash"       gencodec:"required"`
    UncleHash   common.Hash    `json:"sha3Uncles"       gencodec:"required"`
    Coinbase    common.Address `json:"miner"            gencodec:"required"`
    Root        common.Hash    `json:"stateRoot"        gencodec:"required"`
    TxHash      common.Hash    `json:"transactionsRoot" gencodec:"required"`
    ReceiptHash common.Hash    `json:"receiptsRoot"     gencodec:"required"`
    Bloom       Bloom          `json:"logsBloom"        gencodec:"required"`
    Difficulty  *big.Int       `json:"difficulty"       gencodec:"required"`
    Number      *big.Int       `json:"number"           gencodec:"required"`
    GasLimit    uint64         `json:"gasLimit"         gencodec:"required"`
    GasUsed     uint64         `json:"gasUsed"          gencodec:"required"`
    Time        *big.Int       `json:"timestamp"        gencodec:"required"`
    Extra       []byte         `json:"extraData"        gencodec:"required"`
    MixDigest   common.Hash    `json:"mixHash"          gencodec:"required"`
    Nonce       BlockNonce     `json:"nonce"            gencodec:"required"`
}

三颗重要的树

每一棵树的节点都是一个k,v对

状态树

键是地址(账户地址&合约地址),是KEC(a)即160位的账户地址的哈希(Keccak-256算法),value是账户(nonce、balance、storageRoot、codeHash)的RLP格式序列化字节

也就是Account数据结构

core/state/state_object.go:
type Account struct {
    Nonce    uint64
    Balance  *big.Int
    Root     common.Hash // merkle root of the storage trie
    CodeHash []byte
}

交易树

包含此区块所列的所有交易的树的根节点Hash值。

收据树

收据是交易的执行结果。存在的意义在于,交易树表示以太坊状态机的输入,收据树表示以太坊状态机的输出。而状态树,是以太坊账户的最终状态,也可以理解为整个以太坊系统的最终状态。

Transaction Receipts record the transaction outcome
Purpose:
* Transaction Tries: records transaction request vectors
* Transaction Receipt Tries: records the transaction outcome

Parameters used in composing a Transaction Receipt Trie [details in section 4.4.1 of the yellow paper]:

> The post-transaction state: a trie structure holding the state after the execution. Encoded as a byte array.
> Gas used: the total amount of gas used.
> Logs: the set of logs entries created upon transaction execution.
> The bloom filter: it is created based on the information found int he logs. Logs entries are reduced are reduced to 256 bytes hashes, which are embedded in the block header as the logs bloom.

收据的主要字段有:
* blockHash: 交易所在块的哈希值
* blockNumber: 交易在块的序号
* transactionHash: 交易的哈希值
* transactionIndex: 交易在块中的序号
* from: 发送者地址
* to: 接受者地址,为空时候表示创建合约
* cumulativeGasUsed: 执行完此交易时候,块内消耗的总的gas值
* gasUsed:本交易所消耗的gas
* contractAddress: 当此交易为创建合约时,表示所创建合约的地址,否则为空
* logs: 此交易的日志

/core/types/receipt.go
type Receipt struct {
    // Consensus fields
    PostState         []byte `json:"root"`
    Status            uint64 `json:"status"`
    CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
    Bloom             Bloom  `json:"logsBloom"         gencodec:"required"`
    Logs              []*Log `json:"logs"              gencodec:"required"`

    // Implementation fields (don't reorder!)
    TxHash          common.Hash    `json:"transactionHash" gencodec:"required"`
    ContractAddress common.Address `json:"contractAddress"`
    GasUsed         uint64         `json:"gasUsed" gencodec:"required"`
}

其他

  • 梅克尔状态转变的证明(Merkle state transition proof)。“如果你在根S的状态树上运行交易T,其结果状态树将是根为S’,log为L,输出为O”
  • 状态树和收据树,是以通过交易计算得到。严格讲是一种线下状态,但是该状态的footprint被保存在了链上

以太坊原理分析2 账户

账户分为两种:

  • 外部拥有账户(EOA),一般指自然人拥有的账户。
EOA特征
    codeHash为空
    storageRoot为空
    通过私钥控制
    发起交易(转移以太币或触发合约代码)
  • 合约账户(CA),为智能合约分配的账户。
CA特征
    不能发起交易,可以被触发执行合约代码(通过EOA发起的交易或者从其他CA接收的消息调用激活)

账户数据

  • nonce : 外部账户为交易次数,合约账户为创建的合约序号
  • balance : 此地址的以太币余额
  • storageRoot : 账户存储内容组成的默克尔树根的哈希值。
  • codeHash : 账户EVM代码的hash值。合约账户即为合约代码的哈希值,外部账户为空字符串的哈希值

账户数据结构

core/state/state_object.go:
type Account struct {
    Nonce    uint64
    Balance  *big.Int
    Root     common.Hash // merkle root of the storage trie
    CodeHash []byte
}

合约账户

  • 智能合约账户,就是一段代码,也可以理解为一个状态机。
  • 由交易创建
  • 由交易驱动,每次想改变智能合约状态,需要发送一笔交易
  • 合约账户不能主动发起交易,所有交易都由普通账户发出
  • 智能合约由evm直接运行,运行后的结果,可能会改变智能合约的状态,改变普通账户的状态,由每个节点本地运行evm, 只要有相同的输入,就会由相同的输出

storage root

所有智能合约的数据,都存在leveldb本地。数据组成一颗mpt树,树根会被存放在账户中,存放在链上

对于一个fullnode节点,在同步数据的时候,会构建所有的普通账户,所有智能合约账户,以及所有智能合约账户的storage存储

因此,同receipt一样,你可以理解为,所有的storage都存储在链下,但是根存储在链上

Ethereum uses Patricia Trie data structure implemented using Google’s leveldb database. It handles storage of different kinds of data on the Patricia tries – transaction trie, state trie, storage trie and receipts trie. Only the root node hashes of the tries are stored directly in the blockchain as part of each blocks.

A storage trie is where all of the contract data lives. Each Ethereum account has its own storage trie.The trie is updated whenever there is a state change. (i.e. When a change is made to an existing state variable.) All nodes will make the changes to their own copy of the trie when they receive a new block. Each node in the state trie (i.e. the account) has its own storage trie. A transaction trie is per block, and contains a trie of transactions in the block. The receipt trie contains log entries for the transactions in the transactions trie.

https://stackoverflow.com/questions/51034366/ethereum-smart-contract-storage

以太坊原理分析1 架构

网络结构

区块链状态机

  • 区块链是一个去中心化,分布式状态机
  • 从创始区块开始,每一个新区块的加入,就是一个新的状态点
    > 区块的加入:
    image
    > 状态点变迁:
    image

主要组成部分

  • 账户
  • 状态
  • 交易
  • 区块
  • gas费用
  • 挖矿共识
  • 智能合约

link

https://medium.com/@preethikasireddy/how-does-ethereum-work-anyway-22d1df506369

https://zhuanlan.zhihu.com/p/30922425