From edf5ffdfbb62c9063fdcd172fbdcbfe77b054082 Mon Sep 17 00:00:00 2001 From: fjf Date: Thu, 6 Sep 2018 16:38:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=98=9F=E4=BA=91=E9=93=BE?= =?UTF-8?q?=E7=9A=84=E5=B9=B6=E8=A1=8C=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nebulas/img/class-parallel.png | Bin 0 -> 18211 bytes ...44\346\230\223\345\210\206\346\236\220.md" | 965 ++++++++++++++++++ ...64\344\275\223\346\236\266\346\236\204.md" | 2 +- 3 files changed, 966 insertions(+), 1 deletion(-) create mode 100644 nebulas/img/class-parallel.png create mode 100644 "nebulas/\346\230\237\344\272\221\351\223\276\347\232\204\345\271\266\350\241\214\344\272\244\346\230\223\345\210\206\346\236\220.md" diff --git a/nebulas/img/class-parallel.png b/nebulas/img/class-parallel.png new file mode 100644 index 0000000000000000000000000000000000000000..0bbb605ba4c46db27f45252f5efed45de37126a2 GIT binary patch literal 18211 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 literal 0 HcmV?d00001 diff --git "a/nebulas/\346\230\237\344\272\221\351\223\276\347\232\204\345\271\266\350\241\214\344\272\244\346\230\223\345\210\206\346\236\220.md" "b/nebulas/\346\230\237\344\272\221\351\223\276\347\232\204\345\271\266\350\241\214\344\272\244\346\230\223\345\210\206\346\236\220.md" new file mode 100644 index 0000000..6d5f281 --- /dev/null +++ "b/nebulas/\346\230\237\344\272\221\351\223\276\347\232\204\345\271\266\350\241\214\344\272\244\346\230\223\345\210\206\346\236\220.md" @@ -0,0 +1,965 @@ +# 星云链的并行交易分析 + +## 一、并行交易的分类 +
+在当前主流的分片技术中,有网络分片,交易分片和状态分片。网络分片,以太坊的casper(正在开发中),状态分片目前是公认最难也最麻烦的。虽然有一些链做了有益的尝试(如trias等),但整体来说还处于研究中。只有交易分片相对来说比较容易,在老的EOS版本中,也使用过交易分片,而星云链中,则实现了一种交易分片的机制。 +
+交易分片的难点在于处理交易的关系,或者说判断交易的关联性。而区块链的传输的性能和全节点备份机制导致不同节点间对交易处理状态有较大的不同,这也是导致交易分片不容易处理的一个原因。 +
+在星云链中,采用交易的版本比较等方法来实现了一个交易并行的方法。其下将对其源码进行分析,并针对其中的一些具体的问题进行展开描述。 +
+ +## 二、星云链的并行交易 +
+星云链的并行交易在出块的时间进行,当dpos决定产生一个块的时候,会调用交易收集器来决定打包入块的相关的交易。在这其中,为提高速度,使用了多协程并行处理交易的方式。 +
+ +### 1、类图 +
+ +![parallel](img/class-parallel.png) +
+ +### 2、源码分析 +
+从出块开始看下面的代码 +
+ +``` go +//出块代码 +func (dpos *Dpos) newBlock(tail *core.Block, consensusState state.ConsensusState, deadlineInMs int64) (*core.Block, error) { +...... + + block.WorldState().SetConsensusState(consensusState) + block.SetTimestamp(consensusState.TimeStamp()) + block.CollectTransactions(deadlineInMs) +...... + + return block, nil +} +//交易并行代码 +// CollectTransactions and add them to block. +func (block *Block) CollectTransactions(deadlineInMs int64) { + metricsBlockPackTxTime.Update(0) + if block.sealed { + logging.VLog().WithFields(logrus.Fields{ + "block": block, + }).Fatal("Sealed block can't be changed.") + } + + secondInMs := int64(1000) + elapseInMs := deadlineInMs - time.Now().Unix()*secondInMs + logging.VLog().WithFields(logrus.Fields{ + "elapse": elapseInMs, + }).Info("Time to pack txs.") + metricsBlockPackTxTime.Update(elapseInMs) + if elapseInMs <= 0 { + return + } + deadlineTimer := time.NewTimer(time.Duration(elapseInMs) * time.Millisecond) + + pool := block.txPool + + packed := int64(0) + unpacked := int64(0) + + dag := dag.NewDag() + transactions := []*Transaction{} + fromBlacklist := new(sync.Map) + toBlacklist := new(sync.Map) + + // parallelCh is used as access tokens here + parallelCh := make(chan bool, ParallelNum) + // mergeCh is used as lock here + mergeCh := make(chan bool, 1) + over := false + + try := 0 + fetch := 0 + failed := 0 + conflict := 0 + expired := 0 + bucket := len(block.txPool.all) + packing := int64(0) + prepare := int64(0) + execute := int64(0) + update := int64(0) + parallel := 0 + beginAt := time.Now().UnixNano() + + go func() { + for { + mergeCh <- true // lock + if over { + <-mergeCh // unlock + return + } + try++ + tx := pool.PopWithBlacklist(fromBlacklist, toBlacklist) + if tx == nil { + <-mergeCh // unlock + continue + } + + logging.VLog().WithFields(logrus.Fields{ + "tx.hash": tx.hash, + }).Debug("Pop tx.") + + fetch++ + fromBlacklist.Store(tx.from.address.Hex(), true) + fromBlacklist.Store(tx.to.address.Hex(), true) + toBlacklist.Store(tx.from.address.Hex(), true) + toBlacklist.Store(tx.to.address.Hex(), true) + <-mergeCh // lock + + parallelCh <- true // fetch access token + go func() { + parallel++ + startAt := time.Now().UnixNano() + defer func() { + endAt := time.Now().UnixNano() + packing += endAt - startAt + <-parallelCh // release access token + }() + + // step1. prepare execution environment + mergeCh <- true // lock + if over { + expired++ + <-mergeCh // unlock + if err := pool.Push(tx); err != nil { + logging.VLog().WithFields(logrus.Fields{ + "block": block, + "tx": tx, + "err": err, + }).Info("Failed to giveback the tx.") + } + return + } + + prepareAt := time.Now().UnixNano() + txWorldState, err := block.WorldState().Prepare(tx.Hash().String()) + preparedAt := time.Now().UnixNano() + prepare += preparedAt - prepareAt + if err != nil { + logging.VLog().WithFields(logrus.Fields{ + "block": block, + "tx": tx, + "err": err, + }).Info("Failed to prepare tx.") + failed++ + + if err := pool.Push(tx); err != nil { + logging.VLog().WithFields(logrus.Fields{ + "block": block, + "tx": tx, + "err": err, + }).Info("Failed to giveback the tx.") + } + + fromBlacklist.Delete(tx.from.address.Hex()) + fromBlacklist.Delete(tx.to.address.Hex()) + toBlacklist.Delete(tx.from.address.Hex()) + toBlacklist.Delete(tx.to.address.Hex()) + <-mergeCh // unlock + return + } + <-mergeCh // unlock + + defer func() { + if err := txWorldState.Close(); err != nil { + logging.VLog().WithFields(logrus.Fields{ + "block": block, + "tx": tx, + "err": err, + }).Info("Failed to close tx.") + } + }() + + // step2. execute tx. + executeAt := time.Now().UnixNano() + giveback, err := block.ExecuteTransaction(tx, txWorldState) + executedAt := time.Now().UnixNano() + execute += executedAt - executeAt + if err != nil { + logging.VLog().WithFields(logrus.Fields{ + "tx": tx, + "err": err, + "giveback": giveback, + }).Debug("invalid tx.") + unpacked++ + failed++ + + /* if err := txWorldState.Close(); err != nil { + logging.VLog().WithFields(logrus.Fields{ + "block": block, + "tx": tx, + "err": err, + }).Debug("Failed to close tx.") + } */ + + if giveback { + if err := pool.Push(tx); err != nil { + logging.VLog().WithFields(logrus.Fields{ + "block": block, + "tx": tx, + "err": err, + }).Info("Failed to giveback the tx.") + } + } + if err == ErrLargeTransactionNonce { + // as for the transactions from a same account + // we will pop them out of transaction pool order by nonce ascend + // thus, when we find a transaction with a very large nonce + // we won't try to pack other transactions from the same account in the block + // the account will be in our from blacklist util the block is sealed + if !byteutils.Equal(tx.to.address, tx.from.address) { + fromBlacklist.Delete(tx.to.address.Hex()) + } + toBlacklist.Delete(tx.to.address.Hex()) + } else { + fromBlacklist.Delete(tx.from.address.Hex()) + fromBlacklist.Delete(tx.to.address.Hex()) + toBlacklist.Delete(tx.from.address.Hex()) + toBlacklist.Delete(tx.to.address.Hex()) + } + return + } + + // step3. check & update tx + mergeCh <- true // lock + if over { + expired++ + <-mergeCh // unlock + if err := pool.Push(tx); err != nil { + logging.VLog().WithFields(logrus.Fields{ + "block": block, + "tx": tx, + "err": err, + }).Info("Failed to giveback the tx.") + } + return + } + updateAt := time.Now().UnixNano() + dependency, err := txWorldState.CheckAndUpdate() + updatedAt := time.Now().UnixNano() + update += updatedAt - updateAt + if err != nil { + logging.VLog().WithFields(logrus.Fields{ + "tx": tx, + "err": err, + "giveback": giveback, + "dependency": dependency, + }).Info("CheckAndUpdate invalid tx.") + unpacked++ + conflict++ + + if err := pool.Push(tx); err != nil { + logging.VLog().WithFields(logrus.Fields{ + "block": block, + "tx": tx, + "err": err, + }).Info("Failed to giveback the tx.") + } + + fromBlacklist.Delete(tx.from.address.Hex()) + fromBlacklist.Delete(tx.to.address.Hex()) + toBlacklist.Delete(tx.from.address.Hex()) + toBlacklist.Delete(tx.to.address.Hex()) + + <-mergeCh // unlock + return + } + logging.VLog().WithFields(logrus.Fields{ + "tx": tx, + }).Debug("packed tx.") + packed++ + + transactions = append(transactions, tx) + txid := tx.Hash().String() + dag.AddNode(txid) + for _, node := range dependency { + dag.AddEdge(node, txid) + } + fromBlacklist.Delete(tx.from.address.Hex()) + fromBlacklist.Delete(tx.to.address.Hex()) + toBlacklist.Delete(tx.from.address.Hex()) + toBlacklist.Delete(tx.to.address.Hex()) + + <-mergeCh // unlock + return + }() + + if over { + return + } + } + }() + + <-deadlineTimer.C + mergeCh <- true // lock + over = true + block.transactions = transactions + block.dependency = dag + <-mergeCh // unlock + + overAt := time.Now().UnixNano() + size := int64(len(block.transactions)) + if size == 0 { + size = 1 + } + averPacking := packing / size + averPrepare := prepare / size + averExecute := execute / size + averUpdate := update / size + + logging.VLog().WithFields(logrus.Fields{ + "try": try, +...... + "dag": block.dependency, + }).Info("CollectTransactions") +} +``` +
+从上面的代码可以看出,选择交易前先处理各种参数,包括时间的更新,并行锁的生成,特别是dag的生成,它是用来判断交易的关系的一个重要的方式,弹出交易数据。这些相关参数配置完成后,启动一个协程。协程内使用一个无限循环来处理交易,在正式选择交易前要先先处理锁的状态和相关的数据的保存,更新数据抓取计算器。 +
+再次启动一个协程,更新并行引用计数器。处理启动时间和异常机制。然后就真正开始选择入块的交易了。 +
+交易交发的代码共分成了四步,一步一步的进行分析。 +
+1)第一步:准备执行环境 +
+ +``` go + +//world_state.go +func (s *states) Prepare(txid interface{}) (*states, error) { + //MVCCDB版本控制准备,见下面代码分析 + changelog, err := s.changelog.Prepare(txid) + if err != nil { + return nil, err + } + + //状态数据库准备 + stateDB, err := s.stateDB.Prepare(txid) + if err != nil { + return nil, err + } + + // Flush all changes in world state into merkle trie + // make a snapshot of world state + //更新世界状态到MPT同时创建一个世界状态的快照 + if err := s.Flush(); err != nil { + return nil, err + } + + //快照 + //创建一个新的帐户状态--会把依赖的相关TID写入新的交易映射 + accState, err := NewAccountState(s.AccountsRoot(), stateDB) + if err != nil { + return nil, err + } + //新的Tx Root MPT状态-同上 + txsState, err := trie.NewTrie(s.TxsRoot(), stateDB, true) + if err != nil { + return nil, err + } + //新的event root mpt + eventsState, err := trie.NewTrie(s.EventsRoot(), stateDB, true) + if err != nil { + return nil, err + } + //新的共识状态 + consensusState, err := s.consensus.NewState(s.ConsensusRoot(), stateDB, true) + if err != nil { + return nil, err + } + + //返回数据结构 + return &states{ + accState: accState, + txsState: txsState, + eventsState: eventsState, + consensusState: consensusState, + + consensus: s.consensus, + changelog: changelog, + stateDB: stateDB, + innerDB: s.innerDB, + txid: txid, + + gasConsumed: make(map[string]*util.Uint128), + events: make(map[string][]*Event), + }, nil +} + +//mvccdb.go +// Prepare a nested transaction这里返回已有的依赖的TID,后面还会在完成后 +//更新状态时更新依赖部分 +func (db *MVCCDB) Prepare(tid interface{}) (*MVCCDB, error) { + db.mutex.Lock() + defer db.mutex.Unlock() + + //判断交易的状态 + if !db.isInTransaction { + return nil, ErrTransactionNotStarted + } + + if tid == nil { + return nil, ErrTidIsNil + } + + if db.preparedDBs[tid] != nil { + return nil, ErrTidIsExist + } + + //数据库中间状态数据的存储准备--与Tid相关关联 + preparedStagingTable, err := db.stagingTable.Prepare(tid) + if err != nil { + return nil, err + } + + //返回MVCCDB实例 + preparedDB := &MVCCDB{ + tid: tid, + storage: db.storage, + stagingTable: preparedStagingTable, + parentDB: db, + isInTransaction: true, + isPreparedDB: true, + isPreparedDBClosed: false, + preparedDBs: make(map[interface{}]*MVCCDB), + isTrieSameKeyCompatibility: db.isTrieSameKeyCompatibility, + } + + db.preparedDBs[tid] = preparedDB + return preparedDB, nil +} + +``` +
+2) 第二步,执行Tx +
+ +``` go +func (block *Block) ExecuteTransaction(tx *Transaction, ws WorldState) (bool, error) { + //检查交易是否合规 + if giveback, err := CheckTransaction(tx, ws); err != nil { + logging.VLog().WithFields(logrus.Fields{ + "tx": tx, + "err": err, + }).Info("Failed to check transaction") + return giveback, err + } + //检验执行 + if giveback, err := VerifyExecution(tx, block, ws); err != nil { + logging.VLog().WithFields(logrus.Fields{ + "tx": tx, + "err": err, + }).Info("Failed to verify transaction execution") + return giveback, err + } + + //接受交易 + if giveback, err := AcceptTransaction(tx, ws); err != nil { + logging.VLog().WithFields(logrus.Fields{ + "tx": tx, + "err": err, + }).Info("Failed to accept transaction") + return giveback, err + } + + return false, nil +} +// CheckTransaction in a tx world state +func CheckTransaction(tx *Transaction, ws WorldState) (bool, error) { + // check nonce + fromAcc, err := ws.GetOrCreateUserAccount(tx.from.address) + if err != nil { + return true, err + } + + // pass current Nonce.检测随机值 + currentNonce := fromAcc.Nonce() + + if tx.nonce < currentNonce+1 { + // Nonce is too small, won't giveback the tx + return false, ErrSmallTransactionNonce + } else if tx.nonce > currentNonce+1 { + return true, ErrLargeTransactionNonce + } + + return false, nil +} +//检测帐户状态 +func (as *accountState) GetOrCreateUserAccount(addr byteutils.Hash) (Account, error) { + acc, err := as.getAccount(addr) + if err != nil && err != ErrAccountNotFound { + return nil, err + } + if err == ErrAccountNotFound { + acc, err = as.newAccount(addr, nil, nil) + if err != nil { + return nil, err + } + return acc, nil + } + return acc, nil +} +//在MVCCDB中记录相关帐户 +func (s *states) recordAccount(acc Account) (Account, error) { + if err := s.changelog.Put(acc.Address(), acc.Address()); err != nil { + return nil, err + } + return acc, nil +} +``` +
+看一下校验执行,这个比较麻烦: +
+ +``` go +// VerifyExecution transaction and return result. +//校验执行交易并返回结果--这个没有太特别的,除了和世界状态的比较 +func VerifyExecution(tx *Transaction, block *Block, ws WorldState) (bool, error) { + // step0. perpare accounts.上文解析过,查看帐户状态 + fromAcc, err := ws.GetOrCreateUserAccount(tx.from.address) + if err != nil { + return true, err + } + //同理 + toAcc, err := ws.GetOrCreateUserAccount(tx.to.address) + if err != nil { + return true, err + } + + // step1. check balance >= gasLimit * gasPrice + //同以太坊,检测Gas + limitedFee, err := tx.gasLimit.Mul(tx.gasPrice) + if err != nil { + // Gas overflow, won't giveback the tx + return false, ErrGasFeeOverflow + } + if fromAcc.Balance().Cmp(limitedFee) < 0 { + // Balance is smaller than limitedFee, won't giveback the tx + return false, ErrInsufficientBalance + } + + // step2. check gasLimit >= txBaseGas. + //判断块的gasLimit是不是超出 + baseGas, err := tx.GasCountOfTxBase() + if err != nil { + // Gas overflow, won't giveback the tx + return false, ErrGasCntOverflow + } + gasUsed := baseGas + if tx.gasLimit.Cmp(gasUsed) < 0 { + logging.VLog().WithFields(logrus.Fields{ + "error": ErrOutOfGasLimit, + "transaction": tx, + "limit": tx.gasLimit, + "acceptedGas": gasUsed, + }).Debug("Failed to check gasLimit >= txBaseGas.") + // GasLimit is smaller than based tx gas, won't giveback the tx + return false, ErrOutOfGasLimit + } + + // !!!!!!Attention: all txs passed here will be on chain. + //注意:必须是链上交易 + // step3. check payload vaild.交易内容的检测 + payload, payloadErr := tx.LoadPayload() + if payloadErr != nil { + return submitTx(tx, block, ws, gasUsed, payloadErr, "Failed to load payload.", "") + } + + // step4. calculate base gas of payload + payloadGas, err := gasUsed.Add(payload.BaseGasCount()) + if err != nil { + logging.VLog().WithFields(logrus.Fields{ + "err": err, + "tx": tx, + "gasUsed": gasUsed, + "payloadBaseGas": payload.BaseGasCount(), + "block": block, + }).Error("Failed to add payload base gas, unexpected error") + metricsUnexpectedBehavior.Update(1) + return submitTx(tx, block, ws, gasUsed, ErrGasCntOverflow, "Failed to add the count of base payload gas", "") + } + gasUsed = payloadGas + if tx.gasLimit.Cmp(gasUsed) < 0 { + return submitTx(tx, block, ws, tx.gasLimit, ErrOutOfGasLimit, "Failed to check gasLimit >= txBaseGas + payloasBaseGas.", "") + } + + // step5. check balance >= limitedFee + value. and transfer + //帐户是否满足需求 + minBalanceRequired, balanceErr := limitedFee.Add(tx.value) + if balanceErr != nil { + return submitTx(tx, block, ws, gasUsed, ErrGasFeeOverflow, "Failed to add tx.value", "") + } + if fromAcc.Balance().Cmp(minBalanceRequired) < 0 { + return submitTx(tx, block, ws, gasUsed, ErrInsufficientBalance, "Failed to check balance >= gasLimit * gasPrice + value", "") + } + var transferSubErr, transferAddErr error + transferSubErr = fromAcc.SubBalance(tx.value) + if transferSubErr == nil { + transferAddErr = toAcc.AddBalance(tx.value) + } + if transferSubErr != nil || transferAddErr != nil { + logging.VLog().WithFields(logrus.Fields{ + "subErr": transferSubErr, + "addErr": transferAddErr, + "tx": tx, + "fromBalance": fromAcc.Balance(), + "toBalance": toAcc.Balance(), + "block": block, + }).Error("Failed to transfer value, unexpected error") + metricsUnexpectedBehavior.Update(1) + return submitTx(tx, block, ws, gasUsed, ErrInvalidTransfer, "Failed to transfer tx.value", "") + } + + // step6. calculate contract's limited gas + contractLimitedGas, err := tx.gasLimit.Sub(gasUsed) + if err != nil { + logging.VLog().WithFields(logrus.Fields{ + "err": err, + "tx": tx, + "gasUsed": gasUsed, + "block": block, + }).Error("Failed to calculate payload's limit gas, unexpected error") + metricsUnexpectedBehavior.Update(1) + return submitTx(tx, block, ws, tx.gasLimit, ErrOutOfGasLimit, "Failed to calculate payload's limit gas", "") + } + + // step7. execute contract. + //执行合约 + gasExecution, exeResult, exeErr := payload.Execute(contractLimitedGas, tx, block, ws) + if exeErr == ErrUnexpected { + return false, exeErr + } + + // step8. calculate final gas. + allGas, gasErr := gasUsed.Add(gasExecution) + if gasErr != nil { + return submitTx(tx, block, ws, gasUsed, ErrGasCntOverflow, "Failed to add the fee of execution gas", "") + } + if tx.gasLimit.Cmp(allGas) < 0 { + return submitTx(tx, block, ws, tx.gasLimit, ErrOutOfGasLimit, "Failed to check gasLimit >= allGas", "") + } + + // step9. over完成 + return submitTx(tx, block, ws, allGas, exeErr, "Failed to execute payload", exeResult) +} +``` +
+再看一下接受交易: +
+ +``` go +func AcceptTransaction(tx *Transaction, ws WorldState) (bool, error) { + // record tx 转成原始交易 + pbTx, err := tx.ToProto() + if err != nil { + return true, err + } + //解析成字节流 + txBytes, err := proto.Marshal(pbTx) + if err != nil { + return true, err + } + //保存交易 + if err := ws.PutTx(tx.hash, txBytes); err != nil { + return true, err + } + // incre nonce计算随机值 + fromAcc, err := ws.GetOrCreateUserAccount(tx.from.address) + if err != nil { + return true, err + } + //nonce +1 + fromAcc.IncrNonce() + // No error, won't giveback the tx + return false, nil +} +``` +
+完成上述动作后,就可以在交易池中增加Tx了。 +
+3) 第三步:检验并更新MVCCDB +
+仍然是先要控制一下锁,然后开始工作: +
+ +``` go +// Push tx into pool +func (pool *TransactionPool) Push(tx *Transaction) error { + pool.mu.Lock() + defer pool.mu.Unlock() + // add tx log in super node + if pool.bc.superNode == true { + logging.VLog().WithFields(logrus.Fields{ + "tx": tx, + }).Debug("Push tx to transaction pool") + } + + //下面分两处理不支持的Tx的异常 + //if is super node and tx type is deploy, do unsupported keyword checking. + if pool.bc.superNode == true && len(pool.bc.unsupportedKeyword) > 0 && len(tx.Data()) > 0 { + if tx.Type() == TxPayloadDeployType { + data := string(tx.Data()) + keywords := strings.Split(pool.bc.unsupportedKeyword, ",") + for _, keyword := range keywords { + keyword = strings.ToLower(keyword) + if strings.Contains(data, keyword) { + logging.VLog().WithFields(logrus.Fields{ + "tx.hash": tx.hash, + "unsupportedKeyword": keyword, + }).Debug("transaction data has unsupported keyword") + unsupportedKeywordError := fmt.Sprintf("transaction data has unsupported keyword(keyword: %s)", keyword) + return errors.New(unsupportedKeywordError) + } + } + } + } + + //if is super node and tx type is deploy, do unsupported keyword checking. + if pool.bc.superNode == true && len(pool.bc.unsupportedKeyword) > 0 && len(tx.Data()) > 0 { + if tx.Type() == TxPayloadDeployType { + data := string(tx.Data()) + keywords := strings.Split(pool.bc.unsupportedKeyword, ",") + for _, keyword := range keywords { + keyword = strings.ToLower(keyword) + if strings.Contains(data, keyword) { + logging.VLog().WithFields(logrus.Fields{ + "tx": tx, + "unsupportedKeyword": keyword, + }).Debug("transaction data has unsupported keyword") + return ErrUnsupportedKeyword + } + } + } + } + // verify non-dup tx重复判断 + if _, ok := pool.all[tx.hash.Hex()]; ok { + metricsDuplicateTx.Inc(1) + return ErrDuplicatedTransaction + } // ToRefine: refine the lock scope + + // if tx's gasPrice below the pool config lowest gasPrice, return ErrBelowGasPrice + if tx.gasPrice.Cmp(pool.minGasPrice) < 0 { + metricsTxPoolBelowGasPrice.Inc(1) + return ErrBelowGasPrice + } + + if tx.gasLimit.Cmp(util.NewUint128()) <= 0 { + metricsTxPoolGasLimitLessOrEqualToZero.Inc(1) + return ErrGasLimitLessOrEqualToZero + } + + if tx.gasLimit.Cmp(pool.maxGasLimit) > 0 { + metricsTxPoolOutOfGasLimit.Inc(1) + return ErrOutOfGasLimit + } + + // verify hash & sign of tx + if err := tx.VerifyIntegrity(pool.bc.chainID); err != nil { + metricsInvalidTx.Inc(1) + return err + } + + // cache the verified tx + pool.pushTx(tx) + // drop max tx in longest bucket if full + //如果超过极值就把最长时间的删除 + if len(pool.all) > pool.size { + poollen := len(pool.all) + pool.dropTx() + + logging.VLog().WithFields(logrus.Fields{ + "tx": tx.StringWithoutData(), + "size": pool.size, + "bpoolsize": poollen, + "apoolsize": len(pool.all), + "bucketsize": len(pool.buckets), + }).Debug("drop tx") + } + + // trigger pending transaction + event := &state.Event{ + Topic: TopicPendingTransaction, + Data: tx.JSONString(), + } + pool.eventEmitter.Trigger(event) + + return nil +} +``` +
+检测并更新状态: +
+ +``` go +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 +} +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 +} +//MVCCDB的检测和更新--去年让他日志和MPT的更新 +//dependency, err := s.changelog.CheckAndUpdate() +//_, err = s.stateDB.CheckAndUpdate() +// 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.标记冲突数据 + //标准是版本号和相同的KEY + if fromValueItem.isConflict(targetValueItem, tbl.isTrieSameKeyCompatibility) { + conflictKeys[keyStr] = targetValueItem.tid + continue + } + + // 2. record dependentTids. + //标记依赖tid + // 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 + } + + //遍历增加新的tid + tids := make([]interface{}, 0, len(dependentTids)) + for key := range dependentTids { + tids = append(tids, key) + } + + return tids, nil +} +``` +
+再看最后: +
+ +``` go +txid := tx.Hash().String() +dag.AddNode(txid) +for _, node := range dependency { + dag.AddEdge(node, txid) +} +``` +
+将节点放入dag中,dag的特点是快速,但不容易回溯。 +
+通过上面的源码分析,就可以清晰的看到星云链的交易并行其实思路还是比较清晰的,主要原理是利用MVCCDB记录的版本号,在MPT状态树中存储着相关的依赖的tid,在协程中执行交易时,会对其进行判断,如果交易不符合情况,就会返回错误,然后利用内存数据进行回滚。如果成功,则更新新的世界状态和相关的数据、日志。 +
+ +## 三、总结 +星云链从一个方向上解决了交易分片的过程,但是看起来还是略微有些简单粗暴,从源码分析来看,还是可能会产生很多的冗余操作,但这也算是对交易分片的一个非常有益的探索。 +
diff --git "a/nebulas/\346\230\237\344\272\221\351\223\276\347\232\204\346\225\264\344\275\223\346\236\266\346\236\204.md" "b/nebulas/\346\230\237\344\272\221\351\223\276\347\232\204\346\225\264\344\275\223\346\236\266\346\236\204.md" index 23723e6..e8d3b1e 100644 --- "a/nebulas/\346\230\237\344\272\221\351\223\276\347\232\204\346\225\264\344\275\223\346\236\266\346\236\204.md" +++ "b/nebulas/\346\230\237\344\272\221\351\223\276\347\232\204\346\225\264\344\275\223\346\236\266\346\236\204.md" @@ -8,7 +8,7 @@ ### 1、星云指数Nebulas Rank
-星云指数是定义价值尺度 (NR)的指标 ,通过综合考虑链中各个账户的流动性及传播性,NR 试图为每个账户建一个个可信、可计算及可复现的普适价值尺度刻画。可以预见,在NR 之上,通过挖掘更大纵深的价值,星云链的平台上将会涌现更多、更丰富的应⽤。 +星云指数是定义价值尺度 (NR)的指标 ,通过综合考虑链中各个账户的流动性及传播性,NR 试图为每个账户建一个个可信、可计算及可复现的普适价值尺度刻画。可以预见,在NR 之上,通过挖掘更大纵深的价值,星云链的平台上将会涌现更多、更丰富的应用。 ### 2、星云原力Nebulas Force(NF)