星云链并行执行交易逻辑

星云链并行交易执行逻辑

 

总体来说分三步:

第一步准备世界状态

txWorldState, err := block.WorldState().Prepare(tx.Hash().String())

func (ws *worldState) Prepare(txid interface{}) (TxWorldState, error) {

    s, err := ws.states.Prepare(txid)

    if err != nil {

        return nil, err

    }

    txState := &txWorldState{

        states: s,

        txid:   txid,

        parent: ws,

    }

    return txState, nil

}

   这个产生的txWorldState相当于包含三部分内容,一个是使用txid生成的一个新的世界状态,一个之前的总的世界状态ws,一个是txid值。

第二步,执行交易

giveback, err := block.ExecuteTransaction(tx, txWorldState)

   其中执行交易又分三个小步骤。

 

其中VerifyExection方法会执行以下逻辑:

Tx的世界状态将会存入from和to(如果在之前的世界状态不存在该from和to地址)的地址,

fromAcc, err := ws.GetOrCreateUserAccount(tx.from.address)

toAcc, err := ws.GetOrCreateUserAccount(tx.to.address)

记录gas的花费,最终会存入一个gasConsumed的map里。

return ws.RecordGas(tx.from.String(), gasCost)

func (tx *Transaction) recordGas(gasCnt *util.Uint128, ws WorldState) error {

    gasCost, err := tx.GasPrice().Mul(gasCnt)

    if err != nil {

        return err

    }



    return ws.RecordGas(tx.from.String(), gasCost)

}

func (s *states) RecordGas(from string, gas *util.Uint128) error {

    consumed, ok := s.gasConsumed[from]

    if !ok {

        consumed = util.NewUint128()

    }

    consumed, err := consumed.Add(gas)

    if err != nil {

        return err

    }

    s.gasConsumed[from] = consumed

    return nil

}

 记录tx的事件    

 event := &state.Event{

        Topic: TopicTransactionExecutionResult,

        Data:  string(txData),

    }

ws.RecordEvent(tx.hash, event)

而RecordEvent利用v8引擎执行把源码编译成llvm的代码,得到的结果作为event存入世界状态

 

其中AcceptTransaction方法会把tx存入世界状态,并且获取tx的from地址,把地址的随机数加1.

 

第三步:合并世界状态

dependency, err := txWorldState.CheckAndUpdate()



func (tws *txWorldState) CheckAndUpdate() ([]interface{}, error) {

    dependencies, err := tws.states.CheckAndUpdateTo(tws.parent.states)

    if err != nil {

        return nil, err

    }

    tws.parent = nil

    return dependencies, nil

}

 其中CheckAndUpdateTo方法实际上是计算出之前的世界状态和加入了这条tx的世界状态的依赖关系。

func (s *states) CheckAndUpdateTo(parent *states) ([]interface{}, error) {

    dependency, err := s.changelog.CheckAndUpdate()

    if err != nil {

        return nil, err

    }

    _, err = s.stateDB.CheckAndUpdate()

    if err != nil {

        return nil, err

    }

    if err := parent.Replay(s); err != nil {

        return nil, err

    }

    return dependency, nil

}

最后会调用changelLog和stateDB的CheckAndUpdate方法,而CheckAndUpdate方法最终会调用staging_table.go 里的MergeToParent方法

// CheckAndUpdate merge current changes to `FinalVersionizedValues`.

func (db *MVCCDB) CheckAndUpdate() ([]interface{}, error) {

    db.mutex.Lock()

    defer db.mutex.Unlock()



    if !db.isInTransaction {

        return nil, ErrTransactionNotStarted

    }



    if !db.isPreparedDB {

        return nil, ErrDisallowedCallingInNoPreparedDB

    }



    if db.isPreparedDBClosed {

        return nil, ErrPreparedDBIsClosed

    }



    ret, err := db.stagingTable.MergeToParent()



    if err == nil {

        // cleanup.

        db.stagingTable.Purge()

    }



    return ret, err

}



// MergeToParent merge key/value pair of tid to `finalVersionizedValues` which the version of value are the same.

func (tbl *StagingTable) MergeToParent() ([]interface{}, error) {

    if tbl.parentStagingTable == nil {

        return nil, ErrParentStagingTableIsNil

    }



    tbl.parentStagingTable.mutex.Lock()

    defer tbl.parentStagingTable.mutex.Unlock()



    tbl.mutex.Lock()

    defer tbl.mutex.Unlock()



    dependentTids := make(map[interface{}]bool)

    conflictKeys := make(map[string]interface{})



    // 1. check version.

    targetValues := tbl.parentStagingTable.versionizedValues



    for keyStr, fromValueItem := range tbl.versionizedValues {

        targetValueItem := targetValues[keyStr]



        if targetValueItem == nil {

            continue

        }



        // 1. record conflict.

        if fromValueItem.isConflict(targetValueItem, tbl.isTrieSameKeyCompatibility) {

            conflictKeys[keyStr] = targetValueItem.tid

            continue

        }



        // 2. record dependentTids.



        // skip default value loaded from storage.

        if targetValueItem.isDefault() {

            continue

        }



        // ignore same parent tid for dependentTids.

        if targetValueItem.tid == tbl.parentStagingTable.tid {

            continue

        }



        // ignore version check when TrieSameKeyCompatibility is enabled.

        if tbl.isTrieSameKeyCompatibility {

            continue

        }



        dependentTids[targetValueItem.tid] = true

    }



    if len(conflictKeys) > 0 {

        logging.VLog().WithFields(logrus.Fields{

            "tid":          tbl.tid,

            "parentTid":    tbl.parentStagingTable.tid,

            "conflictKeys": conflictKeys,

        }).Debug("Failed to be merged into parent.")

        return nil, ErrStagingTableKeyConfliction

    }



    // 2. merge to final.



    // incr parentStagingTable.globalVersion.

    tbl.parentStagingTable.globalVersion++



    for keyStr, fromValueItem := range tbl.versionizedValues {

        // ignore default value item.

        if fromValueItem.isDefault() {

            continue

        }



        // ignore non-dirty.

        if !fromValueItem.dirty {

            continue

        }



        // merge.

        value := fromValueItem.CloneForMerge(tbl.parentStagingTable.globalVersion)

        targetValues[keyStr] = value

    }



    tids := make([]interface{}, 0, len(dependentTids))

    for key := range dependentTids {

        tids = append(tids, key)

    }



    return tids, nil

}

   这个MergeToParent方法如果发现上一个世界状态和执行完tx的世界状态有冲突,则会直接报错,返回合并失败。记录下该tx,然后把冲突的tx重新放回txpoll池。

这里的tbl.versionizedValues都是在调用staging_table.go的Get方法时生成的,

// Get return value by key. If key does not exist, copy and incr version from `parentStagingTable` to record previous version.

func (tbl *StagingTable) Get(key []byte) (*VersionizedValueItem, error) {

    return tbl.GetByKey(key, true)

}



// GetByKey return value by key. If key does not exist, copy and incr version from `parentStagingTable` to record previous version.

func (tbl *StagingTable) GetByKey(key []byte, loadFromStorage bool) (*VersionizedValueItem, error) {

    // double check lock to prevent dead lock while call MergeToParent().

    tbl.mutex.Lock()

    keyStr := byteutils.Hex(key)

    value := tbl.versionizedValues[keyStr]

    tbl.mutex.Unlock()



    if value == nil {

        var err error

        if tbl.parentStagingTable != nil {

            value, err = tbl.parentStagingTable.GetByKey(key, loadFromStorage)

            if err != nil {

                return nil, err

            }



            // global version of keys are not the same, error.

            if !tbl.disableStrictGlobalVersionCheck && value.globalVersion > tbl.prepareingGlobalVersion {

                return nil, ErrStagingTableKeyConfliction

            }



            value = CloneVersionizedValueItem(tbl.tid, value)



        } else {

            if loadFromStorage {

                // load from storage.

                value, err = tbl.loadFromStorage(key)

                if err != nil && err != storage.ErrKeyNotFound {

                    return nil, err

                }

            } else {

                value = NewDefaultVersionizedValueItem(key, nil, tbl.tid, 0)

            }

        }



        // lock and check again.

        tbl.mutex.Lock()

        regetValue := tbl.versionizedValues[keyStr]

        if regetValue == nil {

            tbl.versionizedValues[keyStr] = value

        }

        tbl.mutex.Unlock()

    }



    return value, nil

}

 这里的Get逻辑是:如果key存在,则返回key的值,如果key不存在,并且key在父的世界状态存在,则返回父世界状态key的值,如果父世界状态不存在,则在本世界状态创建一个新的key,key的value为 nil,版本号为1.

世界状态里的key /value集合可能是:

 From Address:From Address

To Address:To Address

Base58(From Address):gas费用

TxHash:event结果事件

 (1)无依赖的情况:

     之前总的世界状态的key/value为 a:version1 b: version1 1,c: version1 1,如果有一个新的tx1的世界状态为d: version1,则表示这个tx的dag依赖集合为空。

 (2)有依赖的情况:

之前总的世界状态的key/value为 a: version1 b: version1,c: version1, 如果有一个新的tx1的世界状态为c:version1,d:version1,则表示这个tx依赖于之前的世界状态c。(version相同的情况一般发生在只读)Dag图为:

  c ->Tx1

之前总的世界状态的key/value为 a: version1 b: version1,c: version1, 如果有一个新的tx2的世界状态为c:version1,b:version1,则表示这个tx依赖于之前的世界状态c和b。Dag图为:

    c->Tx2

    b->Tx2

 (3)冲突的情况:

之前总的世界状态的key/value为 a: version1 b: version1,c: version1, 如果有一个新的tx1的世界状态为c:version2,d:version1,则表示这个tx和之前的世界状态有冲突,则会直接报错,返回合并失败。记录下该tx,然后把冲突的tx重新放回txpoll池。不会再加入dag。

   worldState里的PutTx方法 

func (s *states) PutTx(txHash byteutils.Hash, txBytes []byte) error {

    _, err := s.txsState.Put(txHash, txBytes)

    if err != nil {

        return err

    }

    return nil

}



第四步:根据第三步得到的依赖的世界状态的key集合,在总dag对象里加入该tx的dag依赖集合。

transactions = append(transactions, tx)

                txid := tx.Hash().String()

                dag.AddNode(txid)

                for _, node := range dependency {

                    dag.AddEdge(node, txid)

                }



// AddNode add node

func (dag *Dag) AddNode(key interface{}) error {

    if _, ok := dag.nodes[key]; ok {

        return ErrKeyIsExisted

    }



    dag.nodes[key] = NewNode(key, dag.index)

    dag.indexs[dag.index] = key

    dag.index++

    return nil

}



// AddEdge add edge fromKey toKey

func (dag *Dag) AddEdge(fromKey, toKey interface{}) error {

    var from, to *Node

    var ok bool



    if from, ok = dag.nodes[fromKey]; !ok {

        return ErrKeyNotFound

    }



    if to, ok = dag.nodes[toKey]; !ok {

        return ErrKeyNotFound

    }



    for _, childNode := range from.children {

        if childNode == to {

            return ErrKeyIsExisted

        }

    }



    dag.nodes[toKey].parentCounter++

    dag.nodes[fromKey].children = append(from.children, to)



    return nil

}

 

 全部执行完后,得到的block的dag依赖集合形如:

   c->Tx1

  c->Tx2

  b ->Tx2

  …

 

这种方法和之前的做法的区别是:通过mvccdb去做,实际上是有锁的,而之前那种做法,实际上通过串行队列来代替了锁。

 

整体总结一下全部逻辑:

  对一个区块里的所有tx直接开n个协程执行,n为区块里的tx数量。

  每个协程的执行过程都会进行以下四步:

  1. 准备世界状态环境,得到使用该txid生成的一个新的世界状态txworldState和一个当前的总的世界状态ws。
  2. 检验tx并执行。会调用v8引擎执行,执行完把相应的数据存入新的世界状态。
  3. 合并新的世界状态到总的世界状态里,并得到该tx相对于当前的总的世界状态的依赖集合。如果遇到状态冲突或者其他异常情况,直接返回报错,把该tx重新放回txpoll。
  4. 循环把该tx和tx的依赖集合加入总的dag对象。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值