《亦來云的側(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: ðCommon.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: ðCommon.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)過程。