go-ethereum 源码 commit hash: 2cd6059e51e823012f756112e6e9b2d2920bab74
如何表示一个账号
源码位置: core/state/state_object.go:100
// Account is the Ethereum consensus representation of accounts.
// These objects are stored in the main account trie.
type Account struct {
Nonce uint64
Balance *big.Int
Root common.Hash // merkle root of the storage trie
CodeHash []byte
}
- Nonce 每次从这个账号发出的交易都会加1,防止重放攻击
- Balance 账号的余额
- Root 如果这是一个普通账户,这个值是空值的hash。如果是一个合约账号,那么这个哈希值就是这个合约代码里面用到的存储组成的MPT树的跟哈希
- CodeHash 如果是智能合约账号,那么通过这个哈希就可以在数据库中直接找到智能合约的字节码
源码位置: core/state/state_object.go:65
// stateObject represents an Ethereum account which is being modified.
//
// The usage pattern is as follows:
// First you need to obtain a state object.
// Account values can be accessed and modified through the object.
// Finally, call CommitTrie to write the modified storage trie into a database.
type stateObject struct {
address common.Address
addrHash common.Hash // hash of ethereum address of the account
data Account
db *StateDB
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by StateDB.Commit.
dbErr error
// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
code Code // contract bytecode, which gets set when code is loaded
originStorage Storage // Storage cache of original entries to dedup rewrites
dirtyStorage Storage // Storage entries that need to be flushed to disk
// Cache flags.
// When an object is marked suicided it will be delete from the trie
// during the "update" phase of the state transition.
dirtyCode bool // true if the code was updated
suicided bool
deleted bool
}
以太坊世界状态的改变是通过操作 stateObject 进行的。这个结构是世界状态MPT树的操作对象。对账号余额的修改,智能合约状态的改变,回滚等操作都通过这个结构进行。
以太坊白皮书说“以太坊是一个基于交易的状态机”,状态的改变都是通过区块中的每一笔交易驱动。通过 stateObject 我们就可以快速的找到一个账号的信息。
一笔交易的内部表示
源码位置: core/types/transaction.go:46
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"`
}
- AccountNonce 账号随机数
- Price 每个gas需要消耗多少以太坊币
- GasLimit 这个操作最多允许消耗的gas
- Recipient 转账的地址或者智能合约地址
- Amount 转账数量
- Payload 如果是普通账户,这个payload就相当于一个备注功能,如果是合约账户那么这个payload就是在调用合约里面的方法,或者是在创建一个智能合约
- V, R, S 对交易的签名
交易进入mempool时的验证
源码位置: core/tx_pool.go:205
// TxPool contains all currently known transactions. Transactions
// enter the pool when they are received from the network or submitted
// locally. They exit the pool when they are included in the blockchain.
//
// The pool separates processable transactions (which can be applied to the
// current state) and future transactions. Transactions move between those
// two states over time as they are received and processed.
type TxPool struct {
config TxPoolConfig
chainconfig *params.ChainConfig
chain blockChain
gasPrice *big.Int
txFeed event.Feed
scope event.SubscriptionScope
chainHeadCh chan ChainHeadEvent
chainHeadSub event.Subscription
signer types.Signer
mu sync.RWMutex
currentState *state.StateDB // Current state in the blockchain head
pendingState *state.ManagedState // Pending state tracking virtual nonces
currentMaxGas uint64 // Current gas limit for transaction caps
locals *accountSet // Set of local transaction to exempt from eviction rules
journal *txJournal // Journal of local transaction to back up to disk
pending map[common.Address]*txList // All currently processable transactions
queue map[common.Address]*txList // Queued but non-processable transactions
beats map[common.Address]time.Time // Last heartbeat from each known account
all *txLookup // All transactions to allow lookups
priced *txPricedList // All transactions sorted by price
wg sync.WaitGroup // for shutdown sync
homestead bool
}
一些重要字段的解释:
- config 关于交易池的一些配置信息,比如: 交易池允许的最大容量、交易池允许的最低gasPrice等信息
- chainConfig 关于区块的一些全局配置信息
- gasPrice gas费用
- chainHeadCh 区块链的head发生了改变,需要通过这个chan通知, head发生改变是因为发生了 chain reorganazition
- queue 这个队列里面保存了账号不可立即执行的交易,也就是nonce不连续的交易
对交易的验证:
// validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size).
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
if tx.Size() > 32*1024 {
return ErrOversizedData
}
// Transactions can't be negative. This may never happen using RLP decoded
// transactions but may occur if you create a transaction using the RPC.
if tx.Value().Sign() < 0 {
return ErrNegativeValue
}
// Ensure the transaction doesn't exceed the current block limit gas.
if pool.currentMaxGas < tx.Gas() {
return ErrGasLimit
}
// Make sure the transaction is signed properly
from, err := types.Sender(pool.signer, tx)
if err != nil {
return ErrInvalidSender
}
// Drop non-local transactions under our own minimal accepted gas price
local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
return ErrUnderpriced
}
// Ensure the transaction adheres to nonce ordering
if pool.currentState.GetNonce(from) > tx.Nonce() {
return ErrNonceTooLow
}
// Transactor should have enough funds to cover the costs
// cost == V + GP * GL
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
}
intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
if err != nil {
return err
}
if tx.Gas() < intrGas {
return ErrIntrinsicGas
}
return nil
}
对交易的验证点:
- 交易的大小不超过 32 KB
- 转账数量不能为负
- 交易的gas数量不能超过区块最大允许的gas数量(BGL, block gas limit)
- 验证交易签名的正确性
- 验证是否满足交易池的配置信息
- 验证nonce的正确性
- 验证账户余额是否足够
- 验证固有gas消耗满足需求
交易在虚拟机中执行
源码位置: core/types/transaction.go:383
// Message is a fully derived transaction and implements core.Message
//
// NOTE: In a future PR this will be removed.
type Message struct {
to *common.Address
from common.Address
nonce uint64
amount *big.Int
gasLimit uint64
gasPrice *big.Int
data []byte
checkNonce bool
}
前面提到的Transaction
结构在虚拟机中执行之前都会转换为 Message
结构:
// AsMessage returns the transaction as a core.Message.
//
// AsMessage requires a signer to derive the sender.
//
// XXX Rename message to something less arbitrary?
func (tx *Transaction) AsMessage(s Signer) (Message, error) {
msg := Message{
nonce: tx.data.AccountNonce,
gasLimit: tx.data.GasLimit,
gasPrice: new(big.Int).Set(tx.data.Price),
to: tx.data.Recipient,
amount: tx.data.Amount,
data: tx.data.Payload,
checkNonce: true,
}
var err error
msg.from, err = Sender(s, tx)
return msg, err
}
源码core/state_processor.go中的 Process
函数第一个参数 block 表示要在EVM中执行的区块,第二个参数 stateDB 是要修改的世界状态数据库, 第三个参数 cfg 是关于EVM的配置信息。Process 函数对区块中的每一笔交易调用 ApplyTransaction 函数。在这个函数中为每一笔交易创建新的虚拟机执行环境。接下来的关键调用链:
ApplyTransaction -> ApplyMessage -> NewStateTransition -> TransitionDb
接下来重点看下 TransitionDb
// TransitionDb will transition the state by applying the current message and
// returning the result including the used gas. It returns an error if failed.
// An error indicates a consensus issue.
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)
}
...
st.refundGas()
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
return ret, st.gasUsed(), vmerr != nil, err
}
在这里会区别对待普通交易和和创建智能合约的交易。evm.Create 根据参数创建智能合约, evm.Call 根据交易信息在虚拟机中执行这笔交易。重点分析下 evm.Call
源码位置:core/vm/evm.go:181
// Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
...
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, to, value, gas)
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
...
ret, err = run(evm, contract, input, false)
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in homestead this also counts for code storage gas errors.
if err != nil {
evm.StateDB.RevertToSnapshot(snapshot)
if err != errExecutionReverted {
contract.UseGas(contract.Gas)
}
}
return ret, contract.Gas, err
}
evm.Transfer 完成以太币的转账操作,然后在stateDB中找到并设置智能合约的代码,最后通过 run 函数在 EVM 中执行智能合约的字节码, 如果执行过程中发生错误则回滚先前的操作, 返回执行结果。至此,这笔交易的执行结果就被写到了区块链中。
总结
以太坊中一笔交易的执行流程大致为:
客户端构造交易 -> 通过p2p网络广播交易 -> 矿工节点收到交易 -> 将交易反序列化为 Transaction
结构 -> 将交易放到mempool -> 矿工挖矿 -> 在EVM中执行这笔交易 -> 交易执行结果写入stateDB
网友评论