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 每次從這個賬號發(fā)出的交易都會加1,防止重放攻擊
- Balance 賬號的余額
- Root 如果這是一個普通賬戶,這個值是空值的hash。如果是一個合約賬號,那么這個哈希值就是這個合約代碼里面用到的存儲組成的MPT樹的跟哈希
- CodeHash 如果是智能合約賬號,那么通過這個哈希就可以在數(shù)據(jù)庫中直接找到智能合約的字節(jié)碼
源碼位置: 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
}
以太坊世界狀態(tài)的改變是通過操作 stateObject 進行的。這個結(jié)構(gòu)是世界狀態(tài)MPT樹的操作對象。對賬號余額的修改,智能合約狀態(tài)的改變,回滾等操作都通過這個結(jié)構(gòu)進行。
以太坊白皮書說“以太坊是一個基于交易的狀態(tài)機”,狀態(tài)的改變都是通過區(qū)塊中的每一筆交易驅(qū)動。通過 stateObject 我們就可以快速的找到一個賬號的信息。
一筆交易的內(nèi)部表示
源碼位置: 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 賬號隨機數(shù)
- Price 每個gas需要消耗多少以太坊幣
- GasLimit 這個操作最多允許消耗的gas
- Recipient 轉(zhuǎn)賬的地址或者智能合約地址
- Amount 轉(zhuǎn)賬數(shù)量
- Payload 如果是普通賬戶,這個payload就相當(dāng)于一個備注功能,如果是合約賬戶那么這個payload就是在調(diào)用合約里面的方法,或者是在創(chuàng)建一個智能合約
- 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 關(guān)于交易池的一些配置信息,比如: 交易池允許的最大容量、交易池允許的最低gasPrice等信息
- chainConfig 關(guān)于區(qū)塊的一些全局配置信息
- gasPrice gas費用
- chainHeadCh 區(qū)塊鏈的head發(fā)生了改變,需要通過這個chan通知, head發(fā)生改變是因為發(fā)生了 chain reorganazition
- queue 這個隊列里面保存了賬號不可立即執(zhí)行的交易,也就是nonce不連續(xù)的交易
對交易的驗證:
// 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
- 轉(zhuǎn)賬數(shù)量不能為負
- 交易的gas數(shù)量不能超過區(qū)塊最大允許的gas數(shù)量(BGL, block gas limit)
- 驗證交易簽名的正確性
- 驗證是否滿足交易池的配置信息
- 驗證nonce的正確性
- 驗證賬戶余額是否足夠
- 驗證固有g(shù)as消耗滿足需求
交易在虛擬機中執(zhí)行
源碼位置: 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結(jié)構(gòu)在虛擬機中執(zhí)行之前都會轉(zhuǎn)換為 Message 結(jié)構(gòu):
// 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 函數(shù)第一個參數(shù) block 表示要在EVM中執(zhí)行的區(qū)塊,第二個參數(shù) stateDB 是要修改的世界狀態(tài)數(shù)據(jù)庫, 第三個參數(shù) cfg 是關(guān)于EVM的配置信息。Process 函數(shù)對區(qū)塊中的每一筆交易調(diào)用 ApplyTransaction 函數(shù)。在這個函數(shù)中為每一筆交易創(chuàng)建新的虛擬機執(zhí)行環(huán)境。接下來的關(guān)鍵調(diào)用鏈:
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
}
在這里會區(qū)別對待普通交易和和創(chuàng)建智能合約的交易。evm.Create 根據(jù)參數(shù)創(chuàng)建智能合約, evm.Call 根據(jù)交易信息在虛擬機中執(zhí)行這筆交易。重點分析下 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 完成以太幣的轉(zhuǎn)賬操作,然后在stateDB中找到并設(shè)置智能合約的代碼,最后通過 run 函數(shù)在 EVM 中執(zhí)行智能合約的字節(jié)碼, 如果執(zhí)行過程中發(fā)生錯誤則回滾先前的操作, 返回執(zhí)行結(jié)果。至此,這筆交易的執(zhí)行結(jié)果就被寫到了區(qū)塊鏈中。
總結(jié)
以太坊中一筆交易的執(zhí)行流程大致為:
客戶端構(gòu)造交易 -> 通過p2p網(wǎng)絡(luò)廣播交易 -> 礦工節(jié)點收到交易 -> 將交易反序列化為 Transaction 結(jié)構(gòu) -> 將交易放到mempool -> 礦工挖礦 -> 在EVM中執(zhí)行這筆交易 -> 交易執(zhí)行結(jié)果寫入stateDB