Ethereum 以太坊智能合約調(diào)用源碼分析


title: Ethereum 以太坊智能合約調(diào)用源碼分析
date: 2018-06-16 09:47:08
tags:
- 以太坊
- 智能合約
categories:
- 區(qū)塊鏈


0 前言

  • 基于 Ethereum Coffice (v.1.8.8) 分析
  • 需要區(qū)塊鏈基礎(chǔ)知識(shí)
  • 有 C++/JAVA 基礎(chǔ)就能理解文中 go 代碼
  • 本文不包含合約部署的前序步驟,即sol源碼編譯打包過(guò)程,請(qǐng)參考網(wǎng)絡(luò)上其他文章
  • 流程圖使用 ProcessOn.com 在線繪制
  • 合約調(diào)用與交易的流程大同小異,可參考之前筆者文章“Ethereum 以太坊智能合約部署源碼分析”

整體流程圖如下所示

智能合約調(diào)用流程圖

1 API 發(fā)起智能合約執(zhí)行交易

在部署好的智能合約上,可以發(fā)起合約調(diào)用。每次合約調(diào)用將生成一筆新的交易,Ethereum 區(qū)塊鏈網(wǎng)絡(luò)需要處理交易上鏈?zhǔn)录O旅媸墙灰装l(fā)起過(guò)程用到的兩個(gè)主要數(shù)據(jù)結(jié)構(gòu):

1.1 相關(guān)數(shù)據(jù)結(jié)構(gòu)

  • 客戶端側(cè)傳輸數(shù)據(jù)結(jié)構(gòu)展示:
/*
file: ethapi\api.go
*/
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"`
    Data  *hexutil.Bytes `json:"data"`
    Input *hexutil.Bytes `json:"input"`
}
  • Ethereum 交易數(shù)據(jù)結(jié)構(gòu)展示:
/*
file: core\types\transaction.go
*/
type Transaction struct {
    data txdata
    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"`
    Amount       *big.Int        `json:"value"    gencodec:"required"`
    Payload      []byte          `json:"input"    gencodec:"required"`

    V *big.Int `json:"v" gencodec:"required"`
    R *big.Int `json:"r" gencodec:"required"`
    S *big.Int `json:"s" gencodec:"required"`

    Hash *common.Hash `json:"hash" rlp:"-"`
}

1.2 流程圖細(xì)節(jié)

  1. ethapi/api.go 識(shí)別到這是一次交易發(fā)送請(qǐng)求,調(diào)用 SendTransaction() 函數(shù),進(jìn)一步向底層發(fā)送消息。首先,函數(shù)會(huì)檢查發(fā)送交易賬戶的合法性,并且為交易設(shè)置一系列的初始值,例如 gas, nonce 等。交易信息填滿后,放到交易池中,等待下一步處理。
    1a. 根據(jù)傳入信息創(chuàng)建 Transaction 。注意此處有個(gè)判斷語(yǔ)句,根據(jù) To 是否有值來(lái)確定是合約創(chuàng)建或調(diào)用。

    if args.To == nil { // 合約調(diào)用時(shí),此處to不為空
        return types.NewContractCreation(...)
    }
    return types.NewTransaction(...)
    

    1b. SignTx() 使用交易發(fā)起者信息對(duì)交易進(jìn)行簽名。此處有wallet的概念,官方源碼注釋中多次提到“USB”。筆者網(wǎng)上檢索后知道這是以太坊推出的硬件錢(qián)包,它是一個(gè)安全存儲(chǔ)私鑰的硬件設(shè)備,通過(guò)USB與電腦連接。在進(jìn)行交易時(shí),發(fā)送交易到 Ethereum 區(qū)塊鏈網(wǎng)絡(luò)時(shí),需要使用硬件錢(qián)包的私鑰進(jìn)行交易簽名。下面引用一段對(duì)于硬件錢(qián)包的說(shuō)明:

    Ledger的硬件錢(qián)包圍繞“安全元件”或安全芯片進(jìn)行架構(gòu)。這是和芯片及PIN支付或SIM卡中使用的相同技術(shù)。這些芯片為物理攻擊提供保護(hù),大大提升私鑰的安全性。 我們的研發(fā)人員來(lái)自安全行業(yè)(曾服務(wù)于數(shù)字安全領(lǐng)域的全球領(lǐng)先企業(yè)Gemalto金雅拓,Oberthur 歐貝特等企業(yè)),我們?cè)谥悄芸ê桶踩度胧讲僮飨到y(tǒng)領(lǐng)域擁有豐富的經(jīng)驗(yàn)。
    go var chainID *big.Int if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { chainID = config.ChainId } signed, err := wallet.SignTx(account, tx, chainID)
    若是檢測(cè)到 Ethereum 軟件未運(yùn)行,將直接反饋空的交易信息
    go /* file: accouts\usbwallet\ledger.go */ if w.offline() { return common.Address{}, nil, accounts.ErrWalletClosed }
    1c. submitTransaction() 將交易推進(jìn)交易池,等待下一步處理。
    go func submitTransaction(...) (common.Hash, error) { if err := b.SendTx(ctx, tx); err != nil { return common.Hash{}, err } if tx.To() == nil { ... addr := crypto.CreateAddress(from, tx.Nonce()) log.Info(... "contract", addr.Hex()) } ... return tx.Hash(), nil }
    入池操作會(huì)調(diào)用如下關(guān)鍵函數(shù)。交易池分為兩個(gè)交易列表, pending 中存放可執(zhí)行的交易, queue 中存放排隊(duì)中暫不可執(zhí)行的交易,他們之間可以相互轉(zhuǎn)換。入池之前,pool.validateTx() 會(huì)對(duì)交易簽名進(jìn)行初步檢查,對(duì)于一筆合法的新交易, pool.enqueueTx() 會(huì)將其先放入 queue 列表,隨后 promoteExecutables() 視條件將其移入 pending 中。
    ```go
    /*
    file: core\tx_pool.go
    */
    func (pool *TxPool) addTx(...) error {
    ...
    replace, err := pool.add(tx, local)
    if err != nil {
    return err
    }

     if !replace {
         from, _ := types.Sender(pool.signer, tx) 
         pool.promoteExecutables([]common.Address{from})
     }
     return nil
    

    }

    
    

2 Worker 交易打包出塊

隨著交易池的增長(zhǎng),礦工服務(wù)根據(jù)一定的條件完成交易打包與出塊操作。生成的塊將在網(wǎng)絡(luò)中通過(guò)P2P服務(wù)傳播。
(細(xì)節(jié)待完成)

3 Blockchain 區(qū)塊上鏈

3.1 概述

在收到網(wǎng)絡(luò)上的傳送來(lái)的區(qū)塊后,觸發(fā) blockchain.goInsertChain() 函數(shù),進(jìn)入?yún)^(qū)塊的處理過(guò)程: 區(qū)塊鏈校驗(yàn) -> 區(qū)塊頭驗(yàn)證->區(qū)塊體驗(yàn)證->交易處理->狀態(tài)驗(yàn)證->區(qū)塊上鏈

/*
file: core\blockchain.go
*/
func (bc *BlockChain) insertChain(chain types.Blocks) (...) {
    for i := 1; i < len(chain); i++ {
        if chain[i].NumberU64() != chain[i-1].NumberU64()+1 || chain[i].ParentHash() != chain[i-1].Hash() {
            ...
            return 0, nil, nil, ...
        }
    }
    ...
    abort, results := bc.engine.VerifyHeaders(...)
    ...
    for i, block := range chain {
        ...
        err := <-results
        if err == nil {
            err = bc.Validator().ValidateBody(block)
        }
        ...
        state, err := state.New(parent.Root(), bc.stateCache)
        if err != nil {
            return i, events, coalescedLogs, err
        }

        receipts, logs, usedGas, err := bc.processor.Process(...)
        if err != nil {
            bc.reportBlock(block, receipts, err)
            return i, events, coalescedLogs, err
        }

        err = bc.Validator().ValidateState(...)
        if err != nil {
            bc.reportBlock(block, receipts, err)
            return i, events, coalescedLogs, err
        }

        status, err := bc.WriteBlockWithState(...)
        if err != nil {
            return i, events, coalescedLogs, err
        }
        switch status {
        case CanonStatTy:
            ...
        case SideStatTy:
            ...
    }
    ...
    return 0, events, coalescedLogs, nil
}
  • Message 數(shù)據(jù)結(jié)構(gòu)展示
type Message struct {
    to         *common.Address
    from       common.Address
    nonce      uint64
    amount     *big.Int
    gasLimit   uint64
    gasPrice   *big.Int
    data       []byte
    checkNonce bool
}

3.2 流程圖細(xì)節(jié)

  1. 交易處理過(guò)程調(diào)用 state_processer.goProcess() 函數(shù)對(duì)區(qū)塊中的所有交易進(jìn)行逐一處理。
/*
file: core\state_processor.go
*/
func (p *StateProcessor) Process(...) (types.Receipts, []*types.Log, uint64, error) {
    ...
    for i, tx := range block.Transactions() {
        statedb.Prepare(tx.Hash(), block.Hash(), i)
        receipt, _, err := ApplyTransaction(...)
        if err != nil {
            return nil, nil, 0, err
        }
        receipts = append(receipts, receipt)
        allLogs = append(allLogs, receipt.Logs...)
    }

    p.engine.Finalize(...)

    return receipts, allLogs, *usedGas, nil
}

2a. AsMessage() 函數(shù)將 Transaction 數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換為 Message 數(shù)據(jù)結(jié)構(gòu)

    msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
    if err != nil {
        return nil, 0, err
    }

2b. newEvm() 為合約的執(zhí)行創(chuàng)建虛擬機(jī)環(huán)境。

    context := NewEVMContext(msg, header, bc, author)
    vmenv := vm.NewEVM(context, statedb, config, cfg)

2c. ApplyMessage() 創(chuàng)建一個(gè)新的狀態(tài)變更實(shí)例,并且調(diào)用 state_transition.goTransitionDb() 函數(shù)應(yīng)用合約代碼到這個(gè)實(shí)例中,發(fā)生實(shí)質(zhì)上的狀態(tài)轉(zhuǎn)移記錄。 TransitionDb() 首先會(huì)進(jìn)行交易執(zhí)行的入口檢查,確認(rèn) nonce, gas 是否合法可用;隨后根據(jù)是合約創(chuàng)建還是合約調(diào)用,選擇 Create() 或者 Call(),在合約部署中,此處會(huì)調(diào)用 Call()

/*
file: core\state_transition.go
*/
func (st *StateTransition) TransitionDb() () {
    if err = st.preCheck(); err != nil {
        return
    }
    ...
    contractCreation := msg.To() == nil
    ...
    if contractCreation {
        ret, _, st.gas, vmerr = evm.Create(...)
    } else {
        st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
        ret, st.gas, vmerr = evm.Call(...)  // 合約調(diào)用
    }
    ...
    return ret, st.gasUsed(), vmerr != nil, err
}

下面進(jìn)入到合約調(diào)用最核心的流程,由 core\vm\evm.goCall() 完成。在真正調(diào)用前,會(huì)做一些基本的合法性檢查,例如余額是否足夠進(jìn)行轉(zhuǎn)賬,賬戶合法性等。注意此處若對(duì)象賬戶不存在,內(nèi)部會(huì)為賬戶地址創(chuàng)建新的賬戶。run() 函數(shù)將執(zhí)行合約代碼,第三個(gè)參數(shù)為nil,代表不調(diào)用合約中的任何函數(shù),只是初始化。

Tips:Ethereum 中,在進(jìn)行賬戶的查詢功能和轉(zhuǎn)賬功能時(shí),都是合約調(diào)用交易。但是我們知道,查詢動(dòng)作實(shí)際無(wú)需上鏈,轉(zhuǎn)賬才需要。Ethereum 把這項(xiàng)控制設(shè)計(jì)在了智能合約的編寫(xiě)中,通過(guò)sol語(yǔ)言的函數(shù)修飾關(guān)鍵字來(lái)控制。

在Solidity中constant、view、pure三個(gè)函數(shù)修飾詞的作用是告訴編譯器,函數(shù)不改變/不讀取狀態(tài)變量,這樣函數(shù)執(zhí)行就可以不消耗gas了,因?yàn)椴恍枰V工來(lái)驗(yàn)證。在Solidity v4.17之前,只有constant,后續(xù)版本將constant拆成了view和pure。view的作用和constant一模一樣,可以讀取狀態(tài)變量但是不能改;pure則更為嚴(yán)格,pure修飾的函數(shù)不能改也不能讀狀態(tài)變量,智能操作函數(shù)內(nèi)部變量,否則編譯通不過(guò)。
作者:Kirn
鏈接:http://m.itdecent.cn/p/52eec7ac5413

/*
file: core\vm\evm.go
*/
func (evm *EVM) Call(...) (...) {
    if evm.vmConfig.NoRecursion && evm.depth > 0 {
        return nil, gas, nil
    }
    if evm.depth > int(params.CallCreateDepth) {
        return nil, gas, ErrDepth
    }
    // 余額是否充足
    if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
        return nil, gas, ErrInsufficientBalance
    }

    var (
        to       = AccountRef(addr)
        snapshot = evm.StateDB.Snapshot()
    )
    if !evm.StateDB.Exist(addr) {
        ...
        // 對(duì)方地址無(wú)賬戶,則創(chuàng)建
        evm.StateDB.CreateAccount(addr)
    }

    // 在DB層面執(zhí)行轉(zhuǎn)賬動(dòng)作
    evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
    // 為當(dāng)前合約執(zhí)行創(chuàng)建一個(gè)臨時(shí)的合約
    contract := NewContract(caller, to, value, gas)
    contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))

    ...

    ret, err = run(evm, contract, input)
    // 調(diào)用失敗,根據(jù)快照回滾數(shù)據(jù)
    if err != nil {
        evm.StateDB.RevertToSnapshot(snapshot)
        ...
    }
    return ret, contract.Gas, err
}
  1. blockchain.goWriteBlockWithState() 將掉用 accessors_chain.go 的 3a. WriteBlock() 3b. WriteReceipts() 寫(xiě)入?yún)^(qū)塊和收據(jù)。

4 API 獲取收據(jù)

4.1 概述

在上面的流程中,交易執(zhí)行完畢后,不會(huì)顯式地向請(qǐng)求端回復(fù)任何交易執(zhí)行的具體情況。在 Ethereum 區(qū)塊鏈網(wǎng)絡(luò)中,需要請(qǐng)求端顯式地請(qǐng)求交易收據(jù),以此來(lái)確認(rèn)交易執(zhí)行的情況。從上一節(jié)的第3步驟可知,交易執(zhí)行收據(jù)已經(jīng)寫(xiě)入了db中,請(qǐng)求端要做的是發(fā)生獲取交易收據(jù)請(qǐng)求。

4.2 流程圖細(xì)節(jié)

  1. ethapi/api.go 識(shí)別到這是一次交易發(fā)送請(qǐng)求,調(diào)用 GetTransactionReceipt() 函數(shù),進(jìn)一步向底層發(fā)送消息。如果收據(jù)中的合約地址不是全0,則認(rèn)為是一次合約部署過(guò)程,填寫(xiě)交易中的合約地址到收據(jù)中供請(qǐng)求端使用。
    4a. 調(diào)用ReadTransaction() 函數(shù),獲取交易的基礎(chǔ)信息
    4b. 調(diào)用GetReceipts() 函數(shù),獲取交易收據(jù),其內(nèi)部會(huì)調(diào)用 ReadHeadNumber()ReadReceipts() 函數(shù)輔助操作。
/*
file: ethapi/api.go
*/
func (s *PublicTransactionPoolAPI) GetTransactionReceipt(...) (...) {
    tx, blockHash, blockNumber, index := rawdb.ReadTransaction(...)
    ...
    receipts, err := s.b.GetReceipts(ctx, blockHash)
    if err != nil {
        return nil, err
    }
    ...
    if receipt.ContractAddress != (common.Address{}) {
        fields["contractAddress"] = receipt.ContractAddress
    }
    return fields, nil
}

——————————THE END——————————

以太坊系列文章:
Ethereum 以太坊節(jié)點(diǎn)啟動(dòng)源碼分析 - FinJmy - 簡(jiǎn)書(shū)
Ethereum 以太坊智能合約部署源碼分析 - FinJmy - 簡(jiǎn)書(shū)
Ethereum 以太坊智能合約調(diào)用源碼分析 - FinJmy - 簡(jiǎn)書(shū)

本文文字和圖片均屬博主原創(chuàng)。未經(jīng)授權(quán),不得轉(zhuǎn)載。

個(gè)人網(wǎng)站:https://jmy5945hh.github.io/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • title: Ethereum以太坊智能合約部署源碼分析date: 2018-05-27 09:49:16tags...
    FinJmy閱讀 1,506評(píng)論 0 2
  • 以太坊(Ethereum ):下一代智能合約和去中心化應(yīng)用平臺(tái) 翻譯:巨蟹 、少平 譯者注:中文讀者可以到以太坊愛(ài)...
    車(chē)圣閱讀 3,928評(píng)論 1 7
  • 她晚上睡得挺晚,努力讓自己過(guò)的很充實(shí),忘掉一些不好的東西,夢(mèng),把她的恐懼放大一兩倍,甚至,,,讓她在夢(mèng)里能感知疼痛...
    有點(diǎn)小脾氣閱讀 287評(píng)論 1 1
  • 身在一個(gè)寫(xiě)字越來(lái)越容易的年代,特別是隨著微信公眾號(hào)的火爆,每個(gè)人都有可能寫(xiě)出10萬(wàn)+的文章,都有可能用自己的思想引...
    我是小粗粗閱讀 567評(píng)論 0 1
  • 事情發(fā)生在都市中心的一所醫(yī)院里,一個(gè)滿身纏著白布的人在角落里的病床上躺著,一旁的醫(yī)生和護(hù)士都在搖頭晃腦。 ...
    Cate魔王閱讀 188評(píng)論 0 2

友情鏈接更多精彩內(nèi)容