交易连接输入ConnectInputs

xiaoxiao2021-02-28  31

Ctransaction:: ConnectInputs对应的处理流程

对交易的输入进行判断,并对交易输入在对应交易输入索引中进行占用(标记为花费),并将对应的交易保存起来

Ctransaction:: ConnectInputs对应的处理流程

源码如下: // 交易输入链接,将对应的交易输入占用对应的交易输入的花费标记 bool CTransaction::ConnectInputs(CTxDB& txdb, map<uint256, CTxIndex>& mapTestPool, CDiskTxPos posThisTx, int nHeight, int64& nFees, bool fBlock, bool fMiner, int64 nMinFee) { // 占用前一个交易对应的花费指针 // Take over previous transactions' spent pointers if (!IsCoinBase()) { int64 nValueIn = 0; for (int i = 0; i < vin.size(); i++) { COutPoint prevout = vin[i].prevout; // Read txindex CTxIndex txindex; bool fFound = true; if (fMiner && mapTestPool.count(prevout.hash)) { // Get txindex from current proposed changes txindex = mapTestPool[prevout.hash]; } else { // Read txindex from txdb fFound = txdb.ReadTxIndex(prevout.hash, txindex); } if (!fFound && (fBlock || fMiner)) return fMiner ? false : error("ConnectInputs() : %s prev tx %s index entry not found", GetHash().ToString().substr(0,6).c_str(), prevout.hash.ToString().substr(0,6).c_str()); // Read txPrev CTransaction txPrev; if (!fFound || txindex.pos == CDiskTxPos(1,1,1)) { // Get prev tx from single transactions in memory CRITICAL_BLOCK(cs_mapTransactions) { if (!mapTransactions.count(prevout.hash)) return error("ConnectInputs() : %s mapTransactions prev not found %s", GetHash().ToString().substr(0,6).c_str(), prevout.hash.ToString().substr(0,6).c_str()); txPrev = mapTransactions[prevout.hash]; } if (!fFound) txindex.vSpent.resize(txPrev.vout.size()); } else { // Get prev tx from disk if (!txPrev.ReadFromDisk(txindex.pos)) return error("ConnectInputs() : %s ReadFromDisk prev tx %s failed", GetHash().ToString().substr(0,6).c_str(), prevout.hash.ToString().substr(0,6).c_str()); } if (prevout.n >= txPrev.vout.size() || prevout.n >= txindex.vSpent.size()) return error("ConnectInputs() : %s prevout.n out of range %d %d %d", GetHash().ToString().substr(0,6).c_str(), prevout.n, txPrev.vout.size(), txindex.vSpent.size()); // If prev is coinbase, check that it's matured if (txPrev.IsCoinBase()) for (CBlockIndex* pindex = pindexBest; pindex && nBestHeight - pindex->nHeight < COINBASE_MATURITY-1; pindex = pindex->pprev) if (pindex->nBlockPos == txindex.pos.nBlockPos && pindex->nFile == txindex.pos.nFile) return error("ConnectInputs() : tried to spend coinbase at depth %d", nBestHeight - pindex->nHeight); // Verify signature if (!VerifySignature(txPrev, *this, i)) return error("ConnectInputs() : %s VerifySignature failed", GetHash().ToString().substr(0,6).c_str()); // Check for conflicts if (!txindex.vSpent[prevout.n].IsNull()) return fMiner ? false : error("ConnectInputs() : %s prev tx already used at %s", GetHash().ToString().substr(0,6).c_str(), txindex.vSpent[prevout.n].ToString().c_str()); // 标记前一个交易对应的交易索引对应的花费标记 // Mark outpoints as spent txindex.vSpent[prevout.n] = posThisTx; // Write back if (fBlock) txdb.UpdateTxIndex(prevout.hash, txindex); else if (fMiner) mapTestPool[prevout.hash] = txindex; nValueIn += txPrev.vout[prevout.n].nValue; } // Tally transaction fees int64 nTxFee = nValueIn - GetValueOut(); if (nTxFee < 0) return error("ConnectInputs() : %s nTxFee < 0", GetHash().ToString().substr(0,6).c_str()); if (nTxFee < nMinFee) return false; nFees += nTxFee; } if (fBlock) { // Add transaction to disk index if (!txdb.AddTxIndex(*this, posThisTx, nHeight)) return error("ConnectInputs() : AddTxPos failed"); } else if (fMiner) { // 如果是矿工,将对应的交易放入对应的交易测试池中 // Add transaction to test pool mapTestPool[GetHash()] = CTxIndex(CDiskTxPos(1,1,1), vout.size()); } return true; }

1.1.2 交易断开连接输入DisconnectInputs

Ctransaction:: DisconnectInputs对应的处理流程

释放交易对应的输入占用的标记,即是释放交易输入对应的交易索引中的标记,并将交易从库或者mapTestPool中进行移除。 Ctransaction:: DisconnectInputs对应的处理流程 源码如下: // 断开连接输入,就是释放交易对应的输入的占用:即是释放交易输入对应的交易索引的标记占用 bool CTransaction::DisconnectInputs(CTxDB& txdb) { // 放弃或者让出前一个交易对应的花费标记指针 // Relinquish previous transactions' spent pointers if (!IsCoinBase()) // 币基 { foreach(const CTxIn& txin, vin) { COutPoint prevout = txin.prevout; // Get prev txindex from disk CTxIndex txindex; // 从数据库中读取对应的交易的索引 if (!txdb.ReadTxIndex(prevout.hash, txindex)) return error("DisconnectInputs() : ReadTxIndex failed"); if (prevout.n >= txindex.vSpent.size()) return error("DisconnectInputs() : prevout.n out of range"); // Mark outpoint as not spent txindex.vSpent[prevout.n].SetNull(); // Write back txdb.UpdateTxIndex(prevout.hash, txindex); } } // 将当前交易从交易索引表中移除 // Remove transaction from index if (!txdb.EraseTxIndex(*this)) return error("DisconnectInputs() : EraseTxPos failed"); return true; }

1.1.3 交易接受处理

CTransaction::AcceptTransaction对应的处理流程

判断交易能不能被接受,如果能接受将对应的交易放入全局变量中mapTransactions,mapNextTx中 CTransaction::AcceptTransaction对应的处理流程 源码如下: // 判断这边交易能不能被接受,如果能接受将对应的交易放入全局变量中mapTransactions,mapNextTx中 bool CTransaction::AcceptTransaction(CTxDB& txdb, bool fCheckInputs, bool* pfMissingInputs) { if (pfMissingInputs) *pfMissingInputs = false; // 币基交易仅仅在块中有效,币基交易不能做为一个单独的交易 // Coinbase is only valid in a block, not as a loose transaction if (IsCoinBase()) return error("AcceptTransaction() : coinbase as individual tx"); if (!CheckTransaction()) return error("AcceptTransaction() : CheckTransaction failed"); // 判断当前交易是否我们已经接收到过了 // Do we already have it? uint256 hash = GetHash(); CRITICAL_BLOCK(cs_mapTransactions) if (mapTransactions.count(hash)) // 判断内存对象map中是否已经存在 return false; if (fCheckInputs) if (txdb.ContainsTx(hash)) // 判断交易db中是否已经存在 return false; // 判断当前交易对象是否和内存中的交易对象列表冲突 // Check for conflicts with in-memory transactions CTransaction* ptxOld = NULL; for (int i = 0; i < vin.size(); i++) { COutPoint outpoint = vin[i].prevout; // 根据当前交易对应的输入交易,获得对应输入交易对应的输出交易 if (mapNextTx.count(outpoint)) { // Allow replacing with a newer version of the same transaction // i ==0 为coinbase,也就是coinbase可以替换 if (i != 0) return false; // 相对于当前交易更老的交易 ptxOld = mapNextTx[outpoint].ptx; if (!IsNewerThan(*ptxOld)) // 判断是否比原来交易更新,通过nSequences判断 return false; for (int i = 0; i < vin.size(); i++) { COutPoint outpoint = vin[i].prevout; // 当前交易的输入在内存对象mapNextTx对应的输出如果都存在,且都指向原来老的交易,则接收此交易 if (!mapNextTx.count(outpoint) || mapNextTx[outpoint].ptx != ptxOld) return false; } break; } } // 对前交易进行校验和设置前交易对应的输出为花费标记 // Check against previous transactions map<uint256, CTxIndex> mapUnused; int64 nFees = 0; if (fCheckInputs && !ConnectInputs(txdb, mapUnused, CDiskTxPos(1,1,1), 0, nFees, false, false)) { if (pfMissingInputs) *pfMissingInputs = true; return error("AcceptTransaction() : ConnectInputs failed %s", hash.ToString().substr(0,6).c_str()); } // 将当前交易存储在内存,如果老的交易存在,则从内存中将对应的交易移除 // Store transaction in memory CRITICAL_BLOCK(cs_mapTransactions) { if (ptxOld) { printf("mapTransaction.erase(%s) replacing with new version\n", ptxOld->GetHash().ToString().c_str()); mapTransactions.erase(ptxOld->GetHash()); } // 将当前交易存储到内存对象中 AddToMemoryPool(); } // 如果老的交易存在,则从钱包中将老的交易移除 / are we sure this is ok when loading transactions or restoring block txes // If updated, erase old tx from wallet if (ptxOld) // 将交易从钱包映射对象mapWallet中移除,同时将交易从CWalletDB中移除 EraseFromWallet(ptxOld->GetHash()); printf("AcceptTransaction(): accepted %s\n", hash.ToString().substr(0,6).c_str()); return true; }

1.2 工作量难度获得

对应的方法是:

// 根据前一个block对应的工作量获取下一个block获取需要的工作量 unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast)

看源码更清晰,主要是保证对应的区块10分钟产生一个,14天更新一下对应的工作量难度(即是产生2016区块就要更新一下工作量难度),源码如下:

// 根据前一个block对应的工作量获取下一个block获取需要的工作量 unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast) { const unsigned int nTargetTimespan = 14 * 24 * 60 * 60; // two weeks const unsigned int nTargetSpacing = 10 * 60; // 10分钟产生一个block // 每隔2016个块对应的工作量难度就需要重新计算一次 const unsigned int nInterval = nTargetTimespan / nTargetSpacing; // 中间隔了多少个block 2016个块 // 说明当前块是一个创世区块,因为当前块对应的前一个区块为空 // Genesis block if (pindexLast == NULL) return bnProofOfWorkLimit.GetCompact(); // 如果不等于0不进行工作量难度改变 // Only change once per interval if ((pindexLast->nHeight+1) % nInterval != 0) return pindexLast->nBits; // 往前推2016个区块 // Go back by what we want to be 14 days worth of blocks const CBlockIndex* pindexFirst = pindexLast; for (int i = 0; pindexFirst && i < nInterval-1; I++) pindexFirst = pindexFirst->pprev; assert(pindexFirst); // 当前区块的前一个区块创建时间 减去 从当前区块向前推2016个区块得到区块创建时间 // Limit adjustment step unsigned int nActualTimespan = pindexLast->nTime - pindexFirst->nTime; printf(" nActualTimespan = %d before bounds\n", nActualTimespan); // 控制目标难度调整的跨度不能太大 if (nActualTimespan < nTargetTimespan/4) nActualTimespan = nTargetTimespan/4; if (nActualTimespan > nTargetTimespan*4) nActualTimespan = nTargetTimespan*4; // 重新目标计算难度:当前区块对应的前一个区块对应的目标难度 * 实际2016区块对应的创建时间间隔 / 目标时间跨度14天 // Retarget CBigNum bnNew; bnNew.SetCompact(pindexLast->nBits); bnNew *= nActualTimespan; bnNew /= nTargetTimespan; // 如果计算的工作量难度(值越大对应的工作难度越小)小于当前对应的工作量难度 if (bnNew > bnProofOfWorkLimit) bnNew = bnProofOfWorkLimit; /// debug print printf("\n\n\nGetNextWorkRequired RETARGET *****\n"); printf("nTargetTimespan = %d nActualTimespan = %d\n", nTargetTimespan, nActualTimespan); printf("Before: x %s\n", pindexLast->nBits, CBigNum().SetCompact(pindexLast->nBits).getuint256().ToString().c_str()); printf("After: x %s\n", bnNew.GetCompact(), bnNew.getuint256().ToString().c_str()); return bnNew.GetCompact(); }

1.3 区块对应的创建时间

在新建区块的时候,要设置对应区块的时间,由于是P2P的,没有中心化节点能够获得对应的时间,所以需要从对应的区块链中区块的时间中取中位数,然后和当前时间去最大值,对应的代码就是:

pblock->nTime = max((pindexPrev ? pindexPrev->GetMedianTimePast()+1 : 0), GetAdjustedTime());

1.4 block接收处理

1.4.1 区块连接处理

对应的方法是:

// 区块链接:每一个交易链接,增加到区块索引链中 bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) { issue here: it doesn't know the version unsigned int nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK) - 1 + GetSizeOfCompactSize(vtx.size()); map<uint256, CTxIndex> mapUnused; int64 nFees = 0; foreach(CTransaction& tx, vtx) { CDiskTxPos posThisTx(pindex->nFile, pindex->nBlockPos, nTxPos); nTxPos += ::GetSerializeSize(tx, SER_DISK); // 对每一个交易进行输入链接判断 if (!tx.ConnectInputs(txdb, mapUnused, posThisTx, pindex->nHeight, nFees, true, false)) return false; } // 币基交易中对应的输出不能大于整个对应的奖励+交易手续费 if (vtx[0].GetValueOut() > GetBlockValue(nFees)) return false; // Update block index on disk without changing it in memory. // The memory index structure will be changed after the db commits. if (pindex->pprev) { // 将当前区块索引 挂在 前一个区块索引之后 CDiskBlockIndex blockindexPrev(pindex->pprev); blockindexPrev.hashNext = pindex->GetBlockHash(); txdb.WriteBlockIndex(blockindexPrev); } // 监视在block中哪些 // Watch for transactions paying to me foreach(CTransaction& tx, vtx) AddToWalletIfMine(tx, this); return true; } image.png

1.4.2区块分叉处理

方法如下:

// 重新组织区块的索引:因为此时已经出现区块链分叉 bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) { printf("*** REORGANIZE ***\n"); // 找到区块分叉点 // Find the fork CBlockIndex* pfork = pindexBest; CBlockIndex* plonger = pindexNew; // 找到主链和分叉链对应的交叉点 while (pfork != plonger) { if (!(pfork = pfork->pprev)) return error("Reorganize() : pfork->pprev is null"); while (plonger->nHeight > pfork->nHeight) if (!(plonger = plonger->pprev)) return error("Reorganize() : plonger->pprev is null"); } // 列举出当前节点认为的最长链中(从当前最长链到交叉点)失去连接的块 // List of what to disconnect vector<CBlockIndex*> vDisconnect; for (CBlockIndex* pindex = pindexBest; pindex != pfork; pindex = pindex->pprev) vDisconnect.push_back(pindex); // 获取需要连接的块,因为自己认为的最长链实际上不是最长链 // List of what to connect vector<CBlockIndex*> vConnect; for (CBlockIndex* pindex = pindexNew; pindex != pfork; pindex = pindex->pprev) vConnect.push_back(pindex); // 因为上面放入的时候是倒着放的,所以这里在将这个逆序,得到正向的 reverse(vConnect.begin(), vConnect.end()); // 释放断链(仅仅释放对应的block链,对应的block索引链还没有释放) // Disconnect shorter branch vector<CTransaction> vResurrect; foreach(CBlockIndex* pindex, vDisconnect) { CBlock block; if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos, true)) return error("Reorganize() : ReadFromDisk for disconnect failed"); if (!block.DisconnectBlock(txdb, pindex)) return error("Reorganize() : DisconnectBlock failed"); // 将释放块中的交易放入vResurrect,等待复活 // Queue memory transactions to resurrect foreach(const CTransaction& tx, block.vtx) if (!tx.IsCoinBase()) vResurrect.push_back(tx); } // 连接最长的分支 // Connect longer branch vector<CTransaction> vDelete; for (int i = 0; i < vConnect.size(); i++) { CBlockIndex* pindex = vConnect[i]; CBlock block; if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos, true)) return error("Reorganize() : ReadFromDisk for connect failed"); if (!block.ConnectBlock(txdb, pindex)) { // 如果block连接失败之后,说明这个block无效,则删除这块之后的分支 // Invalid block, delete the rest of this branch txdb.TxnAbort(); for (int j = i; j < vConnect.size(); j++) { CBlockIndex* pindex = vConnect[j]; pindex->EraseBlockFromDisk(); txdb.EraseBlockIndex(pindex->GetBlockHash()); mapBlockIndex.erase(pindex->GetBlockHash()); delete pindex; } return error("Reorganize() : ConnectBlock failed"); } // 将加入区块链的块中的交易从对应的内存中删除 // Queue memory transactions to delete foreach(const CTransaction& tx, block.vtx) vDelete.push_back(tx); } // 写入最长链 if (!txdb.WriteHashBestChain(pindexNew->GetBlockHash())) return error("Reorganize() : WriteHashBestChain failed"); // Commit now because resurrecting 复活could take some time txdb.TxnCommit(); // 释放对应的块索引链 // Disconnect shorter branch foreach(CBlockIndex* pindex, vDisconnect) if (pindex->pprev) pindex->pprev->pnext = NULL; // 表示这些块没有在主链上 // 形成一条主链的块索引链 // Connect longer branch foreach(CBlockIndex* pindex, vConnect) if (pindex->pprev) pindex->pprev->pnext = pindex; // 从释放链接的分支中获取对应的交易,将这些交易放入对应的全局变量中得到复活 // Resurrect memory transactions that were in the disconnected branch foreach(CTransaction& tx, vResurrect) tx.AcceptTransaction(txdb, false); // 从全局变量中删除那些已经在主链中的交易 // Delete redundant memory transactions that are in the connected branch foreach(CTransaction& tx, vDelete) tx.RemoveFromMemoryPool(); return true; } Reorganize流程

1.4.3将区块新增到区块索引链中

// 将当前区块增加到对应的区块索引链中mapBlockIndex bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) { // Check for duplicate uint256 hash = GetHash(); if (mapBlockIndex.count(hash)) return error("AddToBlockIndex() : %s already exists", hash.ToString().substr(0,14).c_str()); // Construct new block index object CBlockIndex* pindexNew = new CBlockIndex(nFile, nBlockPos, *this); if (!pindexNew) return error("AddToBlockIndex() : new CBlockIndex failed"); map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first; pindexNew->phashBlock = &((*mi).first); map<uint256, CBlockIndex*>::iterator miPrev = mapBlockIndex.find(hashPrevBlock); if (miPrev != mapBlockIndex.end()) { pindexNew->pprev = (*miPrev).second; // 增加前一个区块索引对应的高度 pindexNew->nHeight = pindexNew->pprev->nHeight + 1; } CTxDB txdb; txdb.TxnBegin(); txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew)); // 更新最长链对应的指针 // New best // 新链的高度已经超过主链了(即是新链到创世区块的长度 大于 本节点认为的最长链到创世区块的长度 if (pindexNew->nHeight > nBestHeight) { // 判断是否是创世区块 if (pindexGenesisBlock == NULL && hash == hashGenesisBlock) { pindexGenesisBlock = pindexNew; txdb.WriteHashBestChain(hash); } else if (hashPrevBlock == hashBestChain) { // 如果当前块对应的前一个块是最长的链 // Adding to current best branch if (!ConnectBlock(txdb, pindexNew) || !txdb.WriteHashBestChain(hash)) { txdb.TxnAbort(); pindexNew->EraseBlockFromDisk(); mapBlockIndex.erase(pindexNew->GetBlockHash()); delete pindexNew; return error("AddToBlockIndex() : ConnectBlock failed"); } txdb.TxnCommit(); // 如果在最长链中,才设置对应区块索引的pnext字段,将当前区块索引设置在前一个区块索引的后面 pindexNew->pprev->pnext = pindexNew; // 如果对应的区块已经放入到主链中,则对应的区块交易应该要从本节点保存的交易内存池中删除 // Delete redundant memory transactions foreach(CTransaction& tx, vtx) tx.RemoveFromMemoryPool(); } else { // 当前区块既不是创世区块,且当前区块对应的前一个区块也不在最长主链上的情况 // 再加上新区块所在链的长度大于本节点认为主链的长度,所有将进行分叉处理 // New best branch if (!Reorganize(txdb, pindexNew)) { txdb.TxnAbort(); return error("AddToBlockIndex() : Reorganize failed"); } } // New best link hashBestChain = hash; pindexBest = pindexNew; nBestHeight = pindexBest->nHeight; nTransactionsUpdated++; printf("AddToBlockIndex: new best=%s height=%d\n", hashBestChain.ToString().substr(0,14).c_str(), nBestHeight); } txdb.TxnCommit(); txdb.Close(); // 转播那些到目前为止还没有进入block中的钱包交易 // Relay wallet transactions that haven't gotten in yet if (pindexNew == pindexBest) RelayWalletTransactions();// 在节点之间进行转播 MainFrameRepaint(); return true; } CBlock::AddToBlockIndex

1.4.4区块接受处理

对应的方法如下:

// 判断当前区块能够被接收 bool CBlock::AcceptBlock() { // Check for duplicate uint256 hash = GetHash(); if (mapBlockIndex.count(hash)) return error("AcceptBlock() : block already in mapBlockIndex"); // Get prev block index map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashPrevBlock); if (mi == mapBlockIndex.end()) return error("AcceptBlock() : prev block not found"); CBlockIndex* pindexPrev = (*mi).second; // 当前块创建的时间要大于前一个块对应的中位数时间 // Check timestamp against prev if (nTime <= pindexPrev->GetMedianTimePast()) return error("AcceptBlock() : block's timestamp is too early"); //工作量证明校验:每一个节点自己计算对应的工作量难度 // Check proof of work if (nBits != GetNextWorkRequired(pindexPrev)) return error("AcceptBlock() : incorrect proof of work"); // Write block to history file unsigned int nFile; unsigned int nBlockPos; // 将块信息写入文件中 if (!WriteToDisk(!fClient, nFile, nBlockPos)) return error("AcceptBlock() : WriteToDisk failed"); // 增加块对应的快索引信息 if (!AddToBlockIndex(nFile, nBlockPos)) return error("AcceptBlock() : AddToBlockIndex failed"); if (hashBestChain == hash) RelayInventory(CInv(MSG_BLOCK, hash)); // // Add atoms to user reviews for coins created // vector<unsigned char> vchPubKey; // if (ExtractPubKey(vtx[0].vout[0].scriptPubKey, false, vchPubKey)) // { // unsigned short nAtom = GetRand(USHRT_MAX - 100) + 100; // vector<unsigned short> vAtoms(1, nAtom); // AddAtomsAndPropagate(Hash(vchPubKey.begin(), vchPubKey.end()), vAtoms, true); // } return true; } CBlock::AcceptBlock

2. 源码地址

我对比特币bitcoin-0.1.0源码加了详细的注释,对应的下载地址:https://github.com/lwjaiyjk/bitcoin-comment-0.1.0.git

作者:瑜骐 链接:https://www.jianshu.com/p/d01e4c58eb11 來源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转载请注明原文地址: https://www.6miu.com/read-2624027.html

最新回复(0)