以太坊側(cè)鏈跨鏈轉(zhuǎn)賬實(shí)現(xiàn)原理之充值交易

《亦來云的側(cè)鏈側(cè)鏈白皮書》從理論方面介紹了亦來云的跨鏈轉(zhuǎn)賬原理,本文就從亦來云的以太坊側(cè)鏈的源碼層來看下具體的實(shí)現(xiàn)。

跨鏈轉(zhuǎn)賬的過程分為主鏈到側(cè)鏈的轉(zhuǎn)賬和側(cè)鏈到主鏈的轉(zhuǎn)賬,這里主鏈就是指跑ELA的主鏈,側(cè)鏈就專指以太坊側(cè)鏈。
另外,主鏈到側(cè)鏈的轉(zhuǎn)賬我稱為充值交易,側(cè)鏈到主鏈的轉(zhuǎn)賬稱為提現(xiàn)交易。現(xiàn)在就按這兩部分來看下具體實(shí)現(xiàn)。

充值交易(主鏈到側(cè)鏈的轉(zhuǎn)賬)

亦來云充值交易是直接基于SPV(Simplified Payment Verification,簡(jiǎn)單交易驗(yàn)證)來實(shí)現(xiàn)的,SPV的原理就不多介紹了,只要了解SPV的數(shù)據(jù)是可信的,就可以了。

所以只要相應(yīng)的側(cè)鏈集成了ELA主鏈的SPV模塊,相應(yīng)的側(cè)鏈就可以同步到主鏈的交易數(shù)據(jù)。然后,相應(yīng)側(cè)鏈再根據(jù)相應(yīng)的數(shù)據(jù)在自己鏈上生成一筆轉(zhuǎn)賬交易,就可以實(shí)現(xiàn)充值交易的過程。(聽著是不是很簡(jiǎn)單^^)。

在亦來云跨鏈充值交易中,在主鏈生成的交易稱為TX1,在側(cè)鏈生成的交易稱為TX2。過程如下:

1.用戶通過錢包在主鏈從地址 U 向主鏈上代表側(cè)鏈的地址 S 轉(zhuǎn)賬 n 個(gè) ELA,并在交易中附加上自己在側(cè)鏈的地址 u,發(fā)送到主鏈上,這個(gè)交易標(biāo)記為 TX1.

2.等待主鏈挖礦將TX1打包,并成功廣播到其他節(jié)點(diǎn)。

3.集成在側(cè)鏈的SPV模塊同步到包含TX1的區(qū)塊.并通知側(cè)鏈?zhǔn)盏匠渲到灰?/em>

4.側(cè)鏈從TX1中解析出目標(biāo)地址和轉(zhuǎn)賬金額,根據(jù)自己側(cè)鏈的交易結(jié)構(gòu),構(gòu)造出TX2,給目標(biāo)地址轉(zhuǎn)賬。

5.等待側(cè)鏈挖礦將TX2打包,并驗(yàn)證區(qū)塊,出塊成功,廣播到其他共識(shí)節(jié)點(diǎn)。

6.等待足夠的確認(rèn)后,用戶在錢包上看到的自己的側(cè)鏈地址 u 入賬了 n 個(gè) ETH

過程知道了,我們就按這個(gè)過程看源碼:

一。先看TX1附加的充值交易的數(shù)據(jù)結(jié)構(gòu):

type TransferCrossChainAsset struct {
    CrossChainAddresses []string         //側(cè)鏈的目標(biāo)地址
    OutputIndexes       []uint64         //對(duì)應(yīng)tx output的下標(biāo)值
    CrossChainAmounts   []common.Fixed64 //需要轉(zhuǎn)賬的金額
}

上面的結(jié)構(gòu)體附加在TX1的payload字段中,表示此交易是個(gè)跨鏈交易. 具體字段意義看注釋就行了??梢钥吹絻?nèi)容是數(shù)組切片,表示可以同時(shí)轉(zhuǎn)多個(gè)地址。

我們?cè)倏聪轮麈渼?chuàng)建的TX1的代碼

func createCrossChainTransaction(walletPath string, from string, fee common.Fixed64, lockedUntil uint32,
    crossChainOutputs ...*CrossChainOutput) (*types.Transaction, error) {
    // check output
    if len(crossChainOutputs) == 0 {
        return nil, errors.New("invalid transaction target")
    }

    outputs := make([]*OutputInfo, 0)
    perAccountFee := fee / common.Fixed64(len(crossChainOutputs))

    // create payload
    payload := &payload.TransferCrossChainAsset{}
    for index, output := range crossChainOutputs {
        payload.CrossChainAddresses = append(payload.CrossChainAddresses, output.CrossChainAddress)
        payload.OutputIndexes = append(payload.OutputIndexes, uint64(index))
        payload.CrossChainAmounts = append(payload.CrossChainAmounts, *output.Amount-perAccountFee)
        outputs = append(outputs, &OutputInfo{
            Recipient: output.Recipient,
            Amount:    output.Amount,
        })
    }
    

我們主要看下如何創(chuàng)建payload的,也就是TX1要附加的跨鏈數(shù)據(jù),需要注意的是CrossChainAmounts 的數(shù)據(jù)是每個(gè)output的Amount減去平分的Fee得到的。下面在側(cè)鏈的解析也是要注意的。

二。SPV偵聽處理

SPV不過多介紹,需要說明的是SPV可以從主鏈同步各種類型的交易,如何判斷某個(gè)交易是轉(zhuǎn)給以太坊側(cè)鏈的呢?所以需要給SPV一個(gè)唯一的地址表示側(cè)鏈地址,供SPV過濾交易,同時(shí)也稱為偵聽地址。這個(gè)地址就根據(jù)相應(yīng)側(cè)鏈的創(chuàng)世區(qū)塊的hash生成的。

有了這個(gè)偵聽地址,并集成SPV模塊,我們就可以同步主鏈的交易了。我們要收到充值交易需要在側(cè)鏈端實(shí)現(xiàn)TransactionListener 這個(gè)接口并設(shè)置偵聽地址給SPV模塊。

type listener struct {
    address string   //偵聽地址
    service spv.SPVService
}

func (l *listener) Address() string {
    return l.address
}

func (l *listener) Type() core.TxType {
    return core.TransferCrossChainAsset
}

func (l *listener) Flags() uint64 {
    return spv.FlagNotifyInSyncing | spv.FlagNotifyConfirmed
}

func (l *listener) Notify(id common.Uint256, proof bloom.MerkleProof, tx core.Transaction) {
    // Submit transaction receipt
    log.Info("========================================================================================")
    log.Info("mainchain transaction info")
    log.Info("----------------------------------------------------------------------------------------")
    log.Info(string(tx.String()))
    log.Info("----------------------------------------------------------------------------------------")
    savePayloadInfo(tx, l)
    l.service.SubmitTransactionReceipt(id, tx.Hash())// give spv service a receipt, Indicates receipt of notice
}

其中Notify就是我們收到針對(duì)本側(cè)鏈的交易入口。收到這個(gè)偵聽后,需要調(diào)用SubmitTransactionReceipt給SPV模塊一個(gè)回執(zhí),表示側(cè)鏈端已經(jīng)收入通知,SPV模塊將不會(huì)再進(jìn)行通知

到這里我們已經(jīng)到了充值交易的第3步--側(cè)鏈?zhǔn)盏匠渲到灰?/em>,下面我們看第4步--側(cè)鏈從TX1中解析出目標(biāo)地址和轉(zhuǎn)賬金額,根據(jù)自己側(cè)鏈的交易結(jié)構(gòu),構(gòu)造出TX2,給目標(biāo)地址轉(zhuǎn)賬

1.從TX1中解析出目標(biāo)地址和轉(zhuǎn)賬金額

這部分的內(nèi)容就是savePayloadInfo(tx, l)

//savePayloadInfo save and send spv perception
func savePayloadInfo(elaTx core.Transaction, l *listener) {
    nr := bytes.NewReader(elaTx.Payload.Data(elaTx.PayloadVersion))
    p := new(payload.TransferCrossChainAsset)
    p.Deserialize(nr, elaTx.PayloadVersion)
    var fees []string
    var address []string
    var outputs []string
    //從交易中解析出 手續(xù)費(fèi),轉(zhuǎn)賬金額,和目標(biāo)地址,然后存在數(shù)據(jù)庫(kù)中.
    for i, amount := range p.CrossChainAmounts {
        fees = append(fees, (elaTx.Outputs[i].Value - amount).String())
        outputs = append(outputs, elaTx.Outputs[i].Value.String())
        address = append(address, p.CrossChainAddresses[i])
    }
    addr := strings.Join(address, ",")
    fee := strings.Join(fees, ",")
    output := strings.Join(outputs, ",")
    if spvTxhash == elaTx.Hash().String() {
        return
    }
    spvTxhash = elaTx.Hash().String()
    err := spvTransactiondb.Put([]byte(elaTx.Hash().String()+"Fee"), []byte(fee))

    if err != nil {
        log.Error("SpvServicedb Put Fee: ", "err", err, "elaHash", elaTx.Hash().String())
    }

    err = spvTransactiondb.Put([]byte(elaTx.Hash().String()+"Address"), []byte(addr))

    if err != nil {
        log.Error("SpvServicedb Put Address: ", "err", err, "elaHash", elaTx.Hash().String())
    }
    err = spvTransactiondb.Put([]byte(elaTx.Hash().String()+"Output"), []byte(output))

    if err != nil {
        log.Error("SpvServicedb Put Output: ", "err", err, "elaHash", elaTx.Hash().String())
    }
    if atomic.LoadInt32(&candSend) == 1 {
        from := GetDefaultSingerAddr()
        IteratorUnTransaction(from)
        f, err := common.StringToFixed64(fees[0])
        if err != nil {
            log.Error("SpvSendTransaction Fee StringToFixed64: ", "err", err, "elaHash", elaTx)
            return

        }
        fe := new(big.Int).SetInt64(f.IntValue())
        y := new(big.Int).SetInt64(rate)
        fee := new(big.Int).Mul(fe, y)
        SendTransaction(from, elaTx.Hash().String(), fee)

    } else {
        UpTransactionIndex(elaTx.Hash().String())
    }
    return
}

代碼很簡(jiǎn)單,這個(gè)函數(shù)主要功能就是從TX1的payload中解析出需要?jiǎng)?chuàng)建TX2所需要數(shù)據(jù),包括手續(xù)費(fèi),金額,目標(biāo)賬戶,其中手續(xù)費(fèi)的解析要注意下:是用TX1的TXOUT的Value減去CrossChainAmounts里的值,和構(gòu)造TX1的時(shí)候相對(duì)應(yīng)。在解析完成后,分別以TX1的hash值為Key的前綴將這三個(gè)值存入數(shù)據(jù)庫(kù)中,并根據(jù)canSend標(biāo)志來決定是否構(gòu)造TX2并廣播交易,canSend標(biāo)志是在另一個(gè)協(xié)程內(nèi)根據(jù)出塊的事件通道來設(shè)置的??聪旅娴拇a:


//.....省略代碼
if spvService, err := spv.NewService(spvCfg,client); err != nil {
        utils.Fatalf("SPV service init error: %v", err)
    } else {
        MinedBlockSub := stack.EventMux().Subscribe(events.MinedBlockEvent{})
        go spv.MinedBroadcastLoop(MinedBlockSub)
        spvService.Start()
        log.Info("Mainchain SPV module started successfully!")
    }
//.....省略代碼

//minedBroadcastLoop Mining awareness, eth can initiate a recharge transaction after the block
func MinedBroadcastLoop(minedBlockSub *event.TypeMuxSubscription) {
    var i = 0

    for {
        select {
        case <-minedBlockSub.Chan():
            i++
            if i >= 2 {
                atomic.StoreInt32(&candSend, 1)
                IteratorUnTransaction(GetDefaultSingerAddr())
            }
        case <-time.After(3 * time.Minute):
            i = 0
            atomic.StoreInt32(&candSend, 0)
        }
    }

}

可以看到在啟動(dòng)SPV服務(wù)的時(shí)候就啟動(dòng)的偵聽協(xié)程。并且每3分鐘將i清0,防止i溢出。也就是本節(jié)點(diǎn)每出一個(gè)塊,根據(jù)SPV數(shù)據(jù)庫(kù)里的內(nèi)容構(gòu)造一次TX2,并發(fā)送出去。也就是TX2的構(gòu)造是由當(dāng)值節(jié)點(diǎn)創(chuàng)建并發(fā)送的。
我們就從IteratorUnTransaction開始看:

//IteratorUnTransaction iterates before mining and processes existing spv refill transactions
func IteratorUnTransaction(from ethCommon.Address) {
    muiterator.Lock()
    defer muiterator.Unlock()
    _, ok := blocksigner.Signers[from]  //from地址是否是簽名者,也就是當(dāng)值礦工,只有礦工才有資格發(fā)送這筆交易。
    if !ok {
        log.Error("error signers", from.String())
        return
    }

    if atomic.LoadInt32(&candIterator) == 1 {
        return//如果candIterator 為1,表示正在構(gòu)造TX2,不能重復(fù)構(gòu)造
    }
    atomic.StoreInt32(&candIterator, 1)//設(shè)置candIterator 為1
    go func(addr ethCommon.Address) {
        for {
            // 對(duì) candSend 標(biāo)志做判斷.
            if atomic.LoadInt32(&candSend) == 0 {
                break
            }
            index := GetUnTransactionNum(spvTransactiondb, UnTransactionIndex)
            if index == missingNumber {//表示數(shù)據(jù)庫(kù)里沒內(nèi)容,不需要構(gòu)造交易
                break
            }
            seek := GetUnTransactionNum(spvTransactiondb, UnTransactionSeek)
            if seek == missingNumber {//獲取需要構(gòu)造的交易.
                seek = 1
            }
            if seek == index {
                break//表示已經(jīng)構(gòu)造過了。
            }
            txHash, err := spvTransactiondb.Get(append([]byte(UnTransaction), encodeUnTransactionNumber(seek)...))
            if err != nil {
                log.Error("get UnTransaction ", "err", err, "seek", seek)
                break
            }
            fee, _, _ := FindOutputFeeAndaddressByTxHash(string(txHash))
            if fee.Uint64() <= 0 {
                break
            }//根據(jù)從數(shù)據(jù)庫(kù)里取出的txHash,fee構(gòu)造TX2,并發(fā)送交易.
            SendTransaction(from, string(txHash), fee)
            err = spvTransactiondb.Put([]byte(UnTransactionSeek), encodeUnTransactionNumber(seek+1))//更新seek值。
            log.Info(UnTransactionSeek+"put", "seek", seek+1)
            if err != nil {
                log.Error("UnTransactionIndexPutSeek ", err, seek+1)
                break
            }
            err = spvTransactiondb.Delete(append([]byte(UnTransaction), encodeUnTransactionNumber(seek)...))//從數(shù)據(jù)庫(kù)中刪除已發(fā)送的TX1的hash值。
            log.Info(UnTransaction+"delete", "seek", seek)
            if err != nil {
                log.Error("UnTransactionIndexDeleteSeek ", "err", err, "seek", seek)
                break
            }
        }
        atomic.StoreInt32(&candIterator, 0)//要設(shè)置candIterator為0.
    }(from)
}

//SendTransaction sends a reload transaction to txpool
func SendTransaction(from ethCommon.Address, elaTx string, fee *big.Int) {
    ethTx, err := ipcClient.StorageAt(context.Background(), ethCommon.Address{}, ethCommon.HexToHash("0x"+elaTx), nil)//根據(jù)TX1的hash值獲取存儲(chǔ)數(shù)據(jù),因?yàn)門X1的hash值要做為TX2的data字段存在節(jié)點(diǎn)上。
    if err != nil {
        log.Error(fmt.Sprintf("IpcClient StorageAt: %v", err))
        return
    }
    h := ethCommon.Hash{}
    if ethCommon.BytesToHash(ethTx) != h {//如果ethTx不為空,表示已經(jīng)處理過這個(gè)交易了.這種情況主要發(fā)生在同步到了由其他節(jié)點(diǎn)打包的TX2交易。
        log.Warn("Cross-chain transactions have been processed", "elaHash", elaTx)
        return
    }
    data, err := common.HexStringToBytes(elaTx)//將TX1的hash轉(zhuǎn)為data字節(jié).
    if err != nil {
        log.Error("elaTx HexStringToBytes: "+elaTx, "err", err)
        return
    }
    msg := ethereum.CallMsg{From: from, To: &ethCommon.Address{}, Data: data}
    gasLimit, err := ipcClient.EstimateGas(context.Background(), msg)//構(gòu)造估算GAS MSG
    if err != nil {
        log.Error("IpcClient EstimateGas:", "err", err, "main txhash", elaTx)
        return
    }
    if gasLimit == 0 {
        return
    }

    price := new(big.Int).Quo(fee, new(big.Int).SetUint64(gasLimit))//根據(jù)手續(xù)費(fèi)和gas花費(fèi),計(jì)算gasPrice;
    callmsg := ethereum.TXMsg{From: from, To: &ethCommon.Address{}, Gas: gasLimit, Data: data, GasPrice: price}//構(gòu)造TX2
    hash, err := ipcClient.SendPublicTransaction(context.Background(), callmsg)//調(diào)用RPC發(fā)送交易,返回交易hash
    if err != nil {
        log.Error("IpcClient SendPublicTransaction: ", "err", err)
        return
    }
    log.Info("Cross chain Transaction", "elaTx", elaTx, "ethTh", hash.String())
}

代碼相應(yīng)的地方我做了注釋,這段代碼的意思就是當(dāng)值超級(jí)節(jié)點(diǎn)出塊的時(shí)候,判斷spv數(shù)庫(kù)中里是否有充值交易TX1,如果有就取出來,構(gòu)造TX2交易,并發(fā)送出去。當(dāng)然會(huì)有些條件判斷,大家看代碼和注釋。

看到這里,大家可能會(huì)有疑問?

1.當(dāng)值礦工初始并沒有ETH,打包交易的手續(xù)費(fèi)從哪里扣的?
2.手續(xù)費(fèi)又是交給誰了?
3.其他節(jié)點(diǎn)同步到這個(gè)包括TX2交易的區(qū)塊后,如何驗(yàn)證TX2有效性?
4.轉(zhuǎn)賬交易三要素,from:當(dāng)值礦工,to:空地址,也叫黑洞地址,value:金額。發(fā)現(xiàn)這個(gè)TX2交易并未設(shè)置value?

在上面的代碼中,是當(dāng)值礦工將此MSG發(fā)送出去的,并且會(huì)將此MSG轉(zhuǎn)為transaction并進(jìn)入本地交易池.所以在此礦工打包的時(shí)候,會(huì)從交易池中取出此交易,會(huì)在EVM中執(zhí)行交易,完成交易轉(zhuǎn)換的函數(shù)是TransitionDb()

func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bool, err error) {
    var (
        evm = st.evm
        // vm errors do not effect consensus and are therefor
        // not assigned to err, except for insufficient balance
        // error.
        vmerr         error
        snapshot      = evm.StateDB.Snapshot()
        blackaddr     common.Address
        blackcontract common.Address
    )

    msg := st.msg
    sender := vm.AccountRef(msg.From())
    contractCreation := msg.To() == nil//是否是布署合約
    txhash := hexutil.Encode(msg.Data())
    //recharge tx
    if len(msg.Data()) == 32 && msg.To() != nil && *msg.To() == blackaddr {
        fee, toaddr, output := spv.FindOutputFeeAndaddressByTxHash(txhash)//從spv模塊查看是否有此交易,這也是驗(yàn)證其他節(jié)點(diǎn)同步過來的交易的方式。
        completetxhash := evm.StateDB.GetState(blackaddr, common.HexToHash(txhash))
        if toaddr != blackaddr {
        //completetxhash表示是否處理過此交易了。
            if (completetxhash == common.Hash{}) && output.Cmp(fee) > 0 {
            //跨鏈轉(zhuǎn)賬的時(shí)候,交易發(fā)送者(也是當(dāng)值礦工)沒有ETH,所以為了交易正常執(zhí)行,先給當(dāng)值礦工初值一些ETH,具體值可以設(shè)置
                st.state.AddBalance(st.msg.From(), new(big.Int).SetUint64(evm.ChainConfig().PassBalance))
                defer func() {
                //這是函數(shù)執(zhí)行完后的處理.
                    ethfee := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)
                    //手續(xù)費(fèi)不足,或目標(biāo)地址有問題,則回滾狀態(tài),交易執(zhí)行失敗,否則給當(dāng)值礦工手續(xù)費(fèi) 
                    if fee.Cmp(new(big.Int)) <= 0 || fee.Cmp(ethfee) < 0 || st.state.GetBalance(toaddr).Cmp(fee) < 0 || vmerr != nil {
                        ret = nil
                        usedGas = 0
                        failed = false
                        if err == nil {
                            err = ErrGasLimitReached
                        }
                        evm.StateDB.RevertToSnapshot(snapshot)
                        return
                    } else {
                        //給當(dāng)值礦工手續(xù)費(fèi)
                        st.state.AddBalance(st.msg.From(), fee)
                    }

                    //判斷是否給預(yù)初始值是否增加成功.
                    if st.state.GetBalance(st.msg.From()).Cmp(new(big.Int).SetUint64(evm.ChainConfig().PassBalance)) < 0 {
                        ret = nil
                        usedGas = 0
                        failed = false
                        if err == nil {
                            err = ErrGasLimitReached
                        }
                        evm.StateDB.RevertToSnapshot(snapshot)
                    } else {//TX2執(zhí)行成功后.給當(dāng)值礦工的初始ETH減掉
                        st.state.SubBalance(st.msg.From(), new(big.Int).SetUint64(evm.ChainConfig().PassBalance))
                    }
                }()
            } else {
                return nil, 0, false, ErrMainTxHashPresence
            }
        } else {
            return nil, 0, false, ErrElaToEthAddress
        }
    } else if contractCreation {
        blackcontract = crypto.CreateAddress(sender.Address(), evm.StateDB.GetNonce(sender.Address()))
        //如果布署的合約是我們的提現(xiàn)合約,則也給當(dāng)值礦工初始一些ETH
        if blackcontract.String() == evm.ChainConfig().BlackContractAddr {
            st.state.AddBalance(st.msg.From(), new(big.Int).SetUint64(evm.ChainConfig().PassBalance))
            defer func() {
                fromValue := st.state.GetBalance(st.msg.From())
                passValue := new(big.Int).SetUint64(evm.ChainConfig().PassBalance)
                if fromValue.Cmp(passValue) < 0 {
                    ret = nil
                    usedGas = 0
                    failed = false
                    if err == nil {
                        err = ErrGasLimitReached
                    }
                    evm.StateDB.RevertToSnapshot(snapshot)
                } else {//合約布署成功后,給當(dāng)值礦工的初始ETH減掉
                    st.state.SubBalance(st.msg.From(), new(big.Int).SetUint64(evm.ChainConfig().PassBalance))
                }
            }()
        }
    }

    if err = st.preCheck(); err != nil {
        return
    }
    homestead := st.evm.ChainConfig().IsHomestead(st.evm.BlockNumber)
    istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.BlockNumber)

    // Pay intrinsic gas
    gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul)
    if err != nil {
        return nil, 0, false, err
    }
    if err = st.useGas(gas); err != nil {
        return nil, 0, false, err
    }

    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)
    }
    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, 0, false, vmerr
        }
    }
    st.refundGas()
    if contractCreation && blackcontract.String() == evm.ChainConfig().BlackContractAddr {
        st.state.AddBalance(st.msg.From(), new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
    } else {
        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
}

這個(gè)函數(shù)的功能主要就是完成交易的狀態(tài)轉(zhuǎn)換,這個(gè)狀態(tài)轉(zhuǎn)換分兩種情況,一種是正常轉(zhuǎn)賬,一種是布署合約。布署合約要調(diào)用evm.Create()創(chuàng)建合約,正常轉(zhuǎn)賬和調(diào)用合約都是走evm.Call。所以真正的轉(zhuǎn)賬實(shí)現(xiàn)在以太坊虛擬機(jī)的Call函數(shù)里。Call先不分析,我們先看這里面的處理。這里解決了上面的第1,2問題--手續(xù)費(fèi)的問題。

在TX2創(chuàng)建的時(shí)候,TX2的Data值設(shè)置的是TX1的hash值,并且To的值是空地址(也叫黑洞地址),注意To的值不是nil,所以判斷一個(gè)交易是否是充值交易TX2的判斷條件就是len(msg.Data()) == 32 && msg.To() != nil && msg.To() == blackaddr。通過上面的代碼可以看到,除了這條判斷外,還要解出來msg.Data()值,從自己的spv數(shù)據(jù)庫(kù)里看下能否查到此交易并且未處理此交易。這些條件滿足后,我們會(huì)看到最粗暴的一句代碼:st.state.AddBalance(st.msg.From(), new(big.Int).SetUint64(evm.ChainConfig().PassBalance))。就是這句代碼給了交易構(gòu)造者初始的ETH,并且PassBalance是可以動(dòng)態(tài)設(shè)置的。也可以在配置文件中配置。當(dāng)然,在交易執(zhí)行成功后,還有一句代碼st.state.SubBalance(st.msg.From(), new(big.Int).SetUint64(evm.ChainConfig().PassBalance))*,這句將給加的值給減掉,以實(shí)現(xiàn)token的平衡。所以這就解決第1個(gè)問題。所以布署合約也是同樣的邏輯,在contractCreation的判斷中做了處理,具體看代碼注釋就可以了。

現(xiàn)在我們看第二個(gè)問題,手續(xù)費(fèi)給誰。上面代碼里還有另一句st.state.AddBalance(st.msg.From(), fee),defer 函數(shù)里對(duì)手續(xù)費(fèi),目標(biāo)地址,判斷完成后,如果交易成功,直接將手續(xù)費(fèi)給了msg.From(),也就是交易發(fā)送者,其實(shí)也是當(dāng)值礦工。

其實(shí)上面的代碼也解決了第三個(gè)問題,如果當(dāng)前節(jié)點(diǎn)不是當(dāng)值礦工,在同步到其他節(jié)點(diǎn)的區(qū)塊后,會(huì)遍歷區(qū)塊里的交易,交易的執(zhí)行同樣會(huì)執(zhí)行上面的代碼,這時(shí)該節(jié)點(diǎn)的spv模塊就發(fā)揮作用了。同樣的代碼fee, toaddr, output := spv.FindOutputFeeAndaddressByTxHash(txhash)這句驗(yàn)證,既可以驗(yàn)證自己節(jié)點(diǎn)的交易,也可以驗(yàn)證同步過來的其他節(jié)點(diǎn)的交易,如果本節(jié)點(diǎn)spv未同步到此TX2對(duì)應(yīng)的TX1,則不會(huì)當(dāng)做TX2處理,就當(dāng)做普通的以太坊交易處理了。

所以上面的代碼算是充值交易的核心了。

我們?cè)僮詈笠粋€(gè)問題,tx的value怎樣處理的,這兒的處理就在EVM的Call函數(shù)里,這里也是一個(gè)轉(zhuǎn)賬交易真正實(shí)現(xiàn)的地方。直接看代碼

func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
//是否禁止調(diào)用call了
    if evm.vmConfig.NoRecursion && evm.depth > 0 {
        return nil, gas, nil
    }

    // Fail if we're trying to execute above the call depth limit
    if evm.depth > int(params.CallCreateDepth) {
        return nil, gas, ErrDepth
    }

    var (
        to        = AccountRef(addr)
        snapshot  = evm.StateDB.Snapshot()
        blackAddr common.Address
        txHash    string
    )
    //this is recharge tx
    if blackAddr == addr && len(input) == 32 {
        txHash = hexutil.Encode(input)
        completeTxHash := evm.StateDB.GetState(blackAddr, common.HexToHash(txHash))
        //通過spv數(shù)據(jù)庫(kù)獲取需要轉(zhuǎn)賬的value也就是此處的output
        fee, address, output :=  spv.FindOutputFeeAndaddressByTxHash(txHash)
        addr = address
        //真正要轉(zhuǎn)的value是要從output減去fee的。這也是從TX1創(chuàng)建的時(shí)候決定的。所以此處要判斷output是否大于fee
        if (completeTxHash == common.Hash{} && addr != blackAddr && output.Cmp(fee) > 0) {
            to = AccountRef(addr)
            //計(jì)算value
            value = new(big.Int).Sub(output, fee)
            //構(gòu)造topics
            topics := make([]common.Hash, 5)
            topics[0] = common.HexToHash("0x09f15c376272c265d7fcb47bf57d8f84a928195e6ea156d12f5a3cd05b8fed5a")
            topics[1] = common.HexToHash(caller.Address().String())
            topics[2] = common.HexToHash(txHash)
            topics[3] = common.HexToHash(addr.String())
            topics[4] = common.BigToHash(value)
            //增加轉(zhuǎn)賬日志
            evm.StateDB.AddLog(&types.Log{
                Address:blackAddr,
                Topics:topics,
                Data:nil,
                // This is a non-consensus field, but assigned here because
                // core/state doesn't know the current block number.
                BlockNumber:evm.BlockNumber.Uint64(),
            })
            //注意此處將value加給了交易發(fā)送者,因此下面的Transfer會(huì)從調(diào)用者給目標(biāo)地址再轉(zhuǎn)一遍。這也是正常流程。
            evm.StateDB.AddBalance(caller.Address(), value)
        }
    }
    // Fail if we're trying to transfer more than the available balance
    if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
        return nil, gas, ErrInsufficientBalance
    }

    if !evm.StateDB.Exist(addr) {
        precompiles := PrecompiledContractsHomestead
        if evm.chainRules.IsByzantium {
            precompiles = PrecompiledContractsByzantium
        }
        if evm.chainRules.IsIstanbul {
            precompiles = PrecompiledContractsIstanbul
        }
        if precompiles[addr] == nil && evm.chainRules.IsEIP158 && value.Sign() == 0 {
            // Calling a non existing account, don't do anything, but ping the tracer
            if evm.vmConfig.Debug && evm.depth == 0 {
                evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)
                evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil)
            }
            return nil, gas, nil
        }
        evm.StateDB.CreateAccount(addr)
    }
    //轉(zhuǎn)賬核心代碼。三要素齊了,from,to,value都在此處。在此實(shí)現(xiàn)了真正的轉(zhuǎn)賬
    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))

    // Even if the account has no code, we need to continue because it might be a precompile
    start := time.Now()

    // Capture the tracer start/end events in debug mode
    if evm.vmConfig.Debug && evm.depth == 0 {
        evm.vmConfig.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value)

        defer func() { // Lazy evaluation of the parameters
            evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
        }()
    }
    ret, err = run(evm, contract, input, false)
    //if is withdraw tx, reduce the contract eth. Because the withdrawal transaction is to transfer ETH token to the black contract, the black contract broadcast event
    if to.Address().String() == evm.ChainConfig().BlackContractAddr && err == nil {
        evm.StateDB.SubBalance(to.Address(), value)
    }
    // 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
}

Transfer的實(shí)現(xiàn)。

// Transfer subtracts amount from sender and adds amount to recipient using the given Db
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
    db.SubBalance(sender, amount)
    db.AddBalance(recipient, amount)
}

在此處我也貼出了以太坊Transfer的代碼,因?yàn)橐蕴皇琴~戶模式,所以轉(zhuǎn)賬代碼就是一增一減。我們說回Call函數(shù)。
以太坊的普通轉(zhuǎn)賬和合約執(zhí)行都會(huì)走一遍evm.Call(),我們會(huì)在此處留下交易執(zhí)行的log和topic.模仿了合約執(zhí)行的結(jié)果。具體原因不討論。

這里采用了和其他地方一樣的判斷處理。如果交易的To是空地址(黑洞地址),并且Input(Data)長(zhǎng)度是32(一個(gè)交易hash的長(zhǎng)度)。我們就取出Tx1的hash值,再通過spv模塊進(jìn)行驗(yàn)證,并且取出目標(biāo)地址,和 value。需要說明的是,目標(biāo)地址的value是要用tx1的轉(zhuǎn)賬金額減去手續(xù)費(fèi)的。

計(jì)算出value了。當(dāng)然是要給到目標(biāo)地址to,但以太坊之前的實(shí)現(xiàn)Transfer是要從交易發(fā)起者賬戶給到目標(biāo)賬戶,這是正常流程,但此時(shí)From發(fā)起者并沒有錢,所以我們需要先將value給到發(fā)起者(from),這就是為什么上面的代碼會(huì)將value加給了call.Address()。evm.StateDB.AddBalance(caller.Address(), value)。然后再通過下面的Transfer函數(shù)從發(fā)起者轉(zhuǎn)給子目標(biāo)地址。evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)。

到這里,其實(shí)跨鏈轉(zhuǎn)賬之充值交易就已經(jīng)成功了。同時(shí)我們也把充值交易做了日志存儲(chǔ)。也就是上面的AddLog,其中Topic記錄了轉(zhuǎn)賬過程。
Topic[0] 是一個(gè)hash值,寫死的。表示的一個(gè)合約hash,不能改變。(遺留問題)
topics[1] = common.HexToHash(caller.Address().String())交易發(fā)起者。也就是from;
topics[2] = common.HexToHash(txHash)TX1交易的hash,也就是主鏈的交易hash.
topics[3] = common.HexToHash(addr.String())目標(biāo)地址。
topics[4] = common.BigToHash(value)轉(zhuǎn)賬金額

這些內(nèi)容記在了交易log里。

我們?cè)僮鰝€(gè)對(duì)應(yīng),我們發(fā)現(xiàn)上面的代碼總會(huì)有個(gè)completehash,表示是否執(zhí)行過此交易,這里有獲取,那設(shè)置的地方呢?
其實(shí)就在ApplyTransaction函數(shù)里。也就是在交易執(zhí)行成功后。進(jìn)行判斷然后處理。

func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
    msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
    if err != nil {
        return 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, err
    }

    if tx.To() != nil {
        to := *tx.To()
        var blackAddr common.Address
        if len(tx.Data()) == 32 && to == blackAddr {
            txHash := hexutil.Encode(tx.Data())
            fee, addr, output := spv.FindOutputFeeAndaddressByTxHash(txHash)
            if fee.Cmp(new(big.Int)) > 0 && output.Cmp(new(big.Int)) > 0 && addr != blackAddr {
            //設(shè)置狀態(tài),表示成功執(zhí)行了充值交易.
                statedb.SetState(blackAddr, common.HexToHash(txHash),tx.Hash())
                statedb.SetNonce(blackAddr, statedb.GetNonce(blackAddr) + 1)
            }
        }
    }
    // 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 += 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 whether the root touch-delete accounts.
    receipt := types.NewReceipt(root, failed, *usedGas)
    receipt.TxHash = tx.Hash()
    receipt.GasUsed = 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})
    receipt.BlockHash = statedb.BlockHash()
    receipt.BlockNumber = header.Number
    receipt.TransactionIndex = uint(statedb.TxIndex())

    return receipt, err
}

上面設(shè)置狀態(tài)的代碼應(yīng)該也看到了。和其他地方一樣,先判斷是否是充值交易,并在spv里可以查到。然后就設(shè)置地狀態(tài)
statedb.SetState(blackAddr, common.HexToHash(txHash),tx.Hash())。表示已經(jīng)成功執(zhí)行。

下面就是收集執(zhí)行結(jié)果和日志。并返回了。。

好了。這就是亦來云以太坊側(cè)鏈的充值交易的實(shí)現(xiàn)過程。下篇我們介紹提現(xiàn)過程。

?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 白皮書是區(qū)塊鏈投資項(xiàng)目的必要知識(shí),既是自己投資成功或者失敗判斷的依據(jù),也是可以分享出來方便自己發(fā)現(xiàn)自己?jiǎn)栴}所在。而...
    丁昆朋閱讀 877評(píng)論 0 0
  • 以太坊(Ethereum ):下一代智能合約和去中心化應(yīng)用平臺(tái) 翻譯:巨蟹 、少平 譯者注:中文讀者可以到以太坊愛...
    車圣閱讀 3,928評(píng)論 1 7
  • 【中文版】以太坊白皮書 翻譯:少平、 Seven當(dāng)中本聰在 2009 年 1 月啟動(dòng)比特幣區(qū)塊鏈時(shí),他同時(shí)向世界引...
    __Seven__閱讀 4,453評(píng)論 0 10
  • 手邊有《學(xué)習(xí)之道》這本書,已經(jīng)很長(zhǎng)時(shí)間了。很久之前看過一遍,沒有看懂,就放到一邊不再管了。直到今天又重新拿出來,今...
    阿凌_ee52閱讀 605評(píng)論 0 0
  • 原創(chuàng)文章轉(zhuǎn)載請(qǐng)注明出處,謝謝端午想好好休息一下,于是就沒出去玩了 關(guān)于NSNotification大家一定不會(huì)陌生...
    北辰明閱讀 1,926評(píng)論 2 34

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