23.以太坊源码分析(23)core-state-process源码分析_boss2967的博客-程序员宝宝

技术标签: Ethereum源码分析  

StateTransition

状态转换模型

/*
The State Transitioning Model
状态转换模型
A state transition is a change made when a transaction is applied to the current world state
状态转换 是指用当前的world state来执行交易,并改变当前的world state
The state transitioning model does all all the necessary work to work out a valid new state root.
状态转换做了所有所需的工作来产生一个新的有效的state root
1) Nonce handling  Nonce 处理
2) Pre pay gas     预先支付Gas
3) Create a new state object if the recipient is \0*32 如果接收人是空,那么创建一个新的state object
4) Value transfer  转账
== If contract creation ==
  4a) Attempt to run transaction data 尝试运行输入的数据
  4b) If valid, use result as code for the new state object 如果有效,那么用运行的结果作为新的state object的code
== end ==
5) Run Script section 运行脚本部分
6) Derive new state root 导出新的state root
*/
type StateTransition struct {
    gp         *GasPool   //用来追踪区块内部的Gas的使用情况
    msg        Message      // Message Call
    gas        uint64
    gasPrice   *big.Int     // gas的价格
    initialGas *big.Int     // 最开始的gas
    value      *big.Int     // 转账的值
    data       []byte       // 输入数据
    state      vm.StateDB   // StateDB
    evm        *vm.EVM      // 虚拟机
}

// Message represents a message sent to a contract.
type Message interface {
    From() common.Address
    //FromFrontier() (common.Address, error)
    To() *common.Address    // 

    GasPrice() *big.Int  // Message 的 GasPrice
    Gas() *big.Int      //message 的 GasLimit
    Value() *big.Int

    Nonce() uint64
    CheckNonce() bool
    Data() []byte
}

构造

// NewStateTransition initialises and returns a new state transition object.
func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition {
    return &StateTransition{
        gp:         gp,
        evm:        evm,
        msg:        msg,
        gasPrice:   msg.GasPrice(),
        initialGas: new(big.Int),
        value:      msg.Value(),
        data:       msg.Data(),
        state:      evm.StateDB,
    }
}

执行Message

// ApplyMessage computes the new state by applying the given message
// against the old state within the environment.
// ApplyMessage 通过应用给定的Message 和状态来生成新的状态
// ApplyMessage returns the bytes returned by any EVM execution (if it took place),
// the gas used (which includes gas refunds) and an error if it failed. An error always
// indicates a core error meaning that the message would always fail for that particular
// state and would never be accepted within a block.
// ApplyMessage返回由任何EVM执行(如果发生)返回的字节,
// 使用的Gas(包括Gas退款),如果失败则返回错误。 一个错误总是表示一个核心错误,
// 意味着这个消息对于这个特定的状态将总是失败,并且永远不会在一个块中被接受。
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, *big.Int, bool, error) {
    st := NewStateTransition(evm, msg, gp)

    ret, _, gasUsed, failed, err := st.TransitionDb()
    return ret, gasUsed, failed, err
}

TransitionDb

// TransitionDb will transition the state by applying the current message and returning the result
// including the required gas for the operation as well as the used gas. It returns an error if it
// failed. An error indicates a consensus issue.
// TransitionDb 
func (st *StateTransition) TransitionDb() (ret []byte, requiredGas, usedGas *big.Int, failed bool, err error) {
    if err = st.preCheck(); err != nil {
        return
    }
    msg := st.msg
    sender := st.from() // err checked in preCheck

    homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
    contractCreation := msg.To() == nil // 如果msg.To是nil 那么认为是一个合约创建

    // Pay intrinsic gas
    // TODO convert to uint64
    // 计算最开始的Gas  g0
    intrinsicGas := IntrinsicGas(st.data, contractCreation, homestead)
    if intrinsicGas.BitLen() > 64 {
        return nil, nil, nil, false, vm.ErrOutOfGas
    }
    if err = st.useGas(intrinsicGas.Uint64()); err != nil {
        return nil, nil, nil, false, err
    }

    var (
        evm = st.evm
        // vm errors do not effect consensus and are therefor
        // not assigned to err, except for insufficient balance
        // error.
        vmerr error
    )
    if contractCreation { //如果是合约创建, 那么调用evm的Create方法
        ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
    } else {
        // Increment the nonce for the next transaction
        // 如果是方法调用。那么首先设置sender的nonce。
        st.state.SetNonce(sender.Address(), st.state.GetNonce(sender.Address())+1)
        ret, st.gas, vmerr = evm.Call(sender, st.to().Address(), st.data, st.gas, st.value)
    }
    if vmerr != nil {
        log.Debug("VM returned with error", "err", vmerr)
        // The only possible consensus-error would be if there wasn't
        // sufficient balance to make the transfer happen. The first
        // balance transfer may never fail.
        if vmerr == vm.ErrInsufficientBalance {
            return nil, nil, nil, false, vmerr
        }
    }
    requiredGas = new(big.Int).Set(st.gasUsed()) // 计算被使用的Gas数量

    st.refundGas()  //计算Gas的退费 会增加到 st.gas上面。 所以矿工拿到的是退税后的
    st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(st.gasUsed(), st.gasPrice)) // 给矿工增加收入。
    // requiredGas和gasUsed的区别一个是没有退税的, 一个是退税了的。
    // 看上面的调用 ApplyMessage直接丢弃了requiredGas, 说明返回的是退税了的。
    return ret, requiredGas, st.gasUsed(), vmerr != nil, err
}

关于g0的计算,在黄皮书上由详细的介绍

和黄皮书有一定出入的部分在于if contractCreation && homestead {igas.SetUint64(params.TxGasContractCreation) 这是因为 Gtxcreate+Gtransaction = TxGasContractCreation

func IntrinsicGas(data []byte, contractCreation, homestead bool) *big.Int {
    igas := new(big.Int)
    if contractCreation && homestead {
        igas.SetUint64(params.TxGasContractCreation)
    } else {
        igas.SetUint64(params.TxGas)
    }
    if len(data) > 0 {
        var nz int64
        for _, byt := range data {
            if byt != 0 {
                nz++
            }
        }
        m := big.NewInt(nz)
        m.Mul(m, new(big.Int).SetUint64(params.TxDataNonZeroGas))
        igas.Add(igas, m)
        m.SetInt64(int64(len(data)) - nz)
        m.Mul(m, new(big.Int).SetUint64(params.TxDataZeroGas))
        igas.Add(igas, m)
    }
    return igas
}

执行前的检查

func (st *StateTransition) preCheck() error {
    msg := st.msg
    sender := st.from()

    // Make sure this transaction's nonce is correct
    if msg.CheckNonce() {
        nonce := st.state.GetNonce(sender.Address())
        // 当前本地的nonce 需要和 msg的Nonce一样 不然就是状态不同步了。
        if nonce < msg.Nonce() {
            return ErrNonceTooHigh
        } else if nonce > msg.Nonce() {
            return ErrNonceTooLow
        }
    }
    return st.buyGas()
}

buyGas, 实现Gas的预扣费, 首先就扣除你的GasLimit * GasPrice的钱。 然后根据计算完的状态在退还一部分。

func (st *StateTransition) buyGas() error {
    mgas := st.msg.Gas()
    if mgas.BitLen() > 64 {
        return vm.ErrOutOfGas
    }

    mgval := new(big.Int).Mul(mgas, st.gasPrice)

    var (
        state  = st.state
        sender = st.from()
    )
    if state.GetBalance(sender.Address()).Cmp(mgval) < 0 {
        return errInsufficientBalanceForGas
    }
    if err := st.gp.SubGas(mgas); err != nil { // 从区块的gaspool里面减去, 因为区块是由GasLimit限制整个区块的Gas使用的。 
        return err
    }
    st.gas += mgas.Uint64()

    st.initialGas.Set(mgas)
    state.SubBalance(sender.Address(), mgval)
    // 从账号里面减去 GasLimit * GasPrice
    return nil
}

退税,退税是为了奖励大家运行一些能够减轻区块链负担的指令, 比如清空账户的storage. 或者是运行suicide命令来清空账号。

func (st *StateTransition) refundGas() {
    // Return eth for remaining gas to the sender account,
    // exchanged at the original rate.
    sender := st.from() // err already checked
    remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice)
    // 首先把用户还剩下的Gas还回去。
    st.state.AddBalance(sender.Address(), remaining)

    // Apply refund counter, capped to half of the used gas.
    // 然后退税的总金额不会超过用户Gas总使用的1/2。 
    uhalf := remaining.Div(st.gasUsed(), common.Big2)
    refund := math.BigMin(uhalf, st.state.GetRefund())
    st.gas += refund.Uint64()
    // 把退税的金额加到用户账户上。
    st.state.AddBalance(sender.Address(), refund.Mul(refund, st.gasPrice))

    // Also return remaining gas to the block gas counter so it is
    // available for the next transaction.
    // 同时也把退税的钱还给gaspool给下个交易腾点Gas空间。
    st.gp.AddGas(new(big.Int).SetUint64(st.gas))
}

StateProcessor

StateTransition是用来处理一个一个的交易的。那么StateProcessor就是用来处理区块级别的交易的。

结构和构造

// StateProcessor is a basic Processor, which takes care of transitioning
// state from one point to another.
//
// StateProcessor implements Processor.
type StateProcessor struct {
    config *params.ChainConfig // Chain configuration options
    bc     *BlockChain         // Canonical block chain
    engine consensus.Engine    // Consensus engine used for block rewards
}

// NewStateProcessor initialises a new StateProcessor.
func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *StateProcessor {
    return &StateProcessor{
        config: config,
        bc:     bc,
        engine: engine,
    }
}

Process,这个方法会被blockchain调用。

// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
// Process 根据以太坊规则运行交易信息来对statedb进行状态改变,以及奖励挖矿者或者是其他的叔父节点。
// Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
// Process返回执行过程中累计的收据和日志,并返回过程中使用的Gas。 如果由于Gas不足而导致任何交易执行失败,将返回错误。
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, *big.Int, error) {
    var (
        receipts     types.Receipts
        totalUsedGas = big.NewInt(0)
        header       = block.Header()
        allLogs      []*types.Log
        gp           = new(GasPool).AddGas(block.GasLimit())
    )
    // Mutate the the block and state according to any hard-fork specs
    // DAO 事件的硬分叉处理 
    if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {
        misc.ApplyDAOHardFork(statedb)
    }
    // Iterate over and process the individual transactions
    for i, tx := range block.Transactions() {
        statedb.Prepare(tx.Hash(), block.Hash(), i)
        receipt, _, err := ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, totalUsedGas, cfg)
        if err != nil {
            return nil, nil, nil, err
        }
        receipts = append(receipts, receipt)
        allLogs = append(allLogs, receipt.Logs...)
    }
    // Finalize the block, applying any consensus engine specific extras (e.g. block rewards)
    p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), receipts)
    // 返回收据 日志 总的Gas使用量和nil
    return receipts, allLogs, totalUsedGas, nil
}

ApplyTransaction

// ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed,
// indicating the block was invalid.
ApplyTransaction尝试将事务应用于给定的状态数据库,并使用其环境的输入参数。 
//它返回事务的收据,使用的Gas和错误,如果交易失败,表明块是无效的。

func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg vm.Config) (*types.Receipt, *big.Int, error) {
    // 把交易转换成Message 
    // 这里如何验证消息确实是Sender发送的。 TODO
    msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
    if err != nil {
        return nil, nil, err
    }
    // Create a new context to be used in the EVM environment
    // 每一个交易都创建了新的虚拟机环境。
    context := NewEVMContext(msg, header, bc, author)
    // Create a new environment which holds all relevant information
    // about the transaction and calling mechanisms.
    vmenv := vm.NewEVM(context, statedb, config, cfg)
    // Apply the transaction to the current state (included in the env)
    _, gas, failed, err := ApplyMessage(vmenv, msg, gp)
    if err != nil {
        return nil, nil, err
    }

    // Update the state with pending changes
    // 求得中间状态
    var root []byte
    if config.IsByzantium(header.Number) {
        statedb.Finalise(true)
    } else {
        root = statedb.IntermediateRoot(config.IsEIP158(header.Number)).Bytes()
    }
    usedGas.Add(usedGas, gas)

    // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
    // based on the eip phase, we're passing wether the root touch-delete accounts.
    // 创建一个收据, 用来存储中间状态的root, 以及交易使用的gas
    receipt := types.NewReceipt(root, failed, usedGas)
    receipt.TxHash = tx.Hash()
    receipt.GasUsed = new(big.Int).Set(gas)
    // if the transaction created a contract, store the creation address in the receipt.
    // 如果是创建合约的交易.那么我们把创建地址存储到收据里面.
    if msg.To() == nil {
        receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
    }

    // Set the receipt logs and create a bloom for filtering
    receipt.Logs = statedb.GetLogs(tx.Hash())
    receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
    // 拿到所有的日志并创建日志的布隆过滤器.
    return receipt, gas, err
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/boss2967/article/details/84258581

智能推荐

nginx漏洞复现_as-root的博客-程序员宝宝_nginx复现fastcgi漏洞

Nginx 上 fastcgi_split_path_info 在处理带有 %0a 的请求时,会因为遇到换行符 \n 导致 PATH_INFO 为空。而 php-fpm 在处理 PATH_INFO 为空的情况下,存在逻辑缺陷。攻击者通过精心的构造和利用,可以导致远程代码执行漏洞复现利用大佬的docker不可以远程代码执行:PHP 7.0 版本PHP 7.1 版本PHP 7.2 版...

成为一个优秀的程序猿你必须知道10个技巧 必看!!!_码农啊飞的博客-程序员宝宝

引言 为代码添加注释(Add comments to your code).– 每个人都知道这一点,但不是每个人都会这么做。你有多少次“忘记”添加注释了?确实,注释不会为你的程序增加任何函数功能。但是,有多少次,看到2周前写的代码,你都记不起它是干什么的?你很幸运,那些未注释的代码是你自己写的,你脑海中还会有残存的印象。非常不幸,大多时候,代码是别人写的,并且那个人很可能已经离开公司了。有句...

HTML制作折线图_#面向百度编程的博客-程序员宝宝_html折线图

Echarts资源下载:(转自:版权声明:本文为CSDN博主「青春已被放纵了」的原创文章原文链接:https://blog.csdn.net/qq_36373262/article/details/65635667)可以在这里下载最新资源:https://github.com/ecomfe/echarts/archive/2.2.1.zip 下不了的话就去官网去下。##方法一:自己敲代码...

队列和栈-用两个队列实现栈_TransientYear的博客-程序员宝宝

文章目录题目描述分析代码题目描述用两个队列实现一个栈。思考用两个栈实现一个队列。两个栈实现一个队列分析队列1 :queue1队列2 :queue2进栈操作:元素入空队列1出栈操作:判断如果队列1只有一个元素,则直接出队。否则,把队1中的元素出队并入队2,直到队1中只有一个元素,再直接出队。为了下一次继续操作,互换队1和队2。代码class Stock...

server2016安装mysql_SQL server 2016 安装步骤图文教程_Ziang Li的博客-程序员宝宝

1.进入安装中心:可以参考硬件和软件要求、可以看到一些说明文档2.选择全新安装模式继续安装3.输入产品秘钥:这里使用演示秘钥进行4.在协议中,点击同意,并点击下一步按钮,继续安装5.进入全局规则检查项,这里可能要花费几秒钟,试具体情况而定6.配置更新项,推荐检查更新7.选择安装更新的具体内容8.安装程序文件9.安装规则检查10.安装功能选择11.实例配置,使用默认即可(如果之前安装过,这里需要特别...

urlencode与unquote_哦...的博客-程序员宝宝_decode unquote urlencode

原文地址当url地址含有中文或者“/”的时候,这是就需要用做urlencode一下编码转换。一、urlencodeurlencode的参数是词典,它可以将key-value这样的键值对转换成我们想要的格式。如果你用的是python2.*,urlencode在urllib.urlencode。如果使用的是python3,urlencode在urllib.parse.urlencode例...

随便推点

Android APP 抓包_faith3389的博客-程序员宝宝_app抓包

目录0.前言1.归因2.抓不到2.1 路由重定向+透明代理2.2 强制全局代理2.3 VPN3.抓到了,然后呢?3.1 系统校验证书3.2 App自校验证书3.3 双向校验4.其他5.尾声6.参考0.前言实习一个半月,习得了一些App抓包姿势,于是自己总结了一套思路。第一次写技术文,欢迎各位批评指正。1.归因App抓包有两种失败:1、笑死,根本抓不到。即丝毫抓不到我们想要的数据包。2、抓到了,但又没抓到。通常表现为Burp中弹Aler.

Python-OpenCV设置摄像头分辨率_hui3909的博客-程序员宝宝

最近玩OpenCV用到一个双目摄像头,需要调整分辨率为1280×480,研究了一下官方API,找到了设置方法,记录一下,以防忘记import cv2cap = cv2.VideoCapture(0)cap.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH,1280)cap.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT,480)w

Oracle 18c 数据库中scott用户不存在的解决方法_weixin_33840661的博客-程序员宝宝

Oracle 18c 数据库中scott用户不存在的解决方法注:该文为转载 上面标题可直接跳转  原文地址:http://www.cnblogs.com/zangdalei/p/5482732.html-- 使用超级管理员登录CONN sys/change_on_install AS SYSDBA ;-- 创建c##scott用户CREATE USER c##scott I...

在win10系统中SQL server2008防火墙阻止了远程调试该怎么办?_高明懿大可爱的博客-程序员宝宝_为远程调试配置防火墙

**在win10系统中SQL server2008防火墙阻止了远程调试该怎么办?**这几天也一直被阻止远程调试的问题所困扰,但我上网查了很多资料,发现他们说话都说的不怎么清楚,那种感觉遮遮掩掩的感觉对博主我这种笨人是相当的不友好,看了半天还是没调出来,之后我自己结合他们所说的,自己渐渐摸索出来,现在我将自己的方法一步一个截图的分享给大家,希望能帮助到在看到这篇文章时和当时的我同样身处于困惑中...

数字范围的+- 1与字母ascii码(2)_qq_33612402的博客-程序员宝宝

关于range range(n)是0~n-1 range(a,n)是a ~n-1我想到一个关于取字符串子串的方法for i in range(len(s)+1):for j in range(i+1,len(s)+1):a = s[i:j]这里的+1是为了使从零开始的range和从1开始的s【】适配这个+1的思考来源是首先手写简单的字符串“abc”所以子串是“a b c ...

推荐文章

热门文章

相关标签