比特币源码解读之选币

(本文使用的是比特币v0.1.0版本 点击下载源码)

本文主要描述交易是如何发起后,选择该账号下根据所有未花费的交易(UTXO)选择最合适的交易组合,作为本次交易的输入。其选取原则是
(1)如果存在一条未花费的交易的值等于输出值,则直接选择它;
(2)针对小于输出值的输入交易,对这些输入交易求和,如果求和值小于输出值,则判断是否存在大于输出值的交易(存在多条时,选择最接近输出值的那条交易);如果存在,则选择该交易;否则返回选取交易失败。
(3)在所有小于输出值的输入交易的交易中,采用随机逼近算法,选择最合适的组合作为本次交易的输入。
流程图如下所示:

初步选择合适交易

 
 
  1. setCoinsRet.clear(); 清空存放交易记录的set
  2. // List of values less than target
  3. int64 nLowestLarger = _I64_MAX; 保存最大的金额值
  4. CWalletTx* pcoinLowestLarger = NULL; 保存金额大于nTargetValue 所有值中的最小值
  5. vector<pair<int64, CWalletTx*> > vValue; 保存满足要求的交易记录,允许key重复(即候选对象)
  6. int64 nTotalLower = 0; 存放小于nTargetValue的所有值之和

遍历钱包中的所有交易

 
 
  1. CRITICAL_BLOCK(cs_mapWallet)
  2. {
  3. for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it) 循环遍历钱包中的所有交易
  4. {
  5. CWalletTx* pcoin = &(*it).second; 选取钱包的交易
  6. ...
  7. }
  8. }

交易有效性检查

 
 
  1. if (!pcoin->IsFinal() || pcoin->fSpent) 判断该交易是否结束或者被花费
  2. continue; 如果是,则取出下一条记录
  3. int64 n = pcoin->GetCredit(); 获取记录可用金额
  4. if (n <= 0) 可用金额小于0,则取出下一条记录
  5. continue;

交易归类

所有未花费的交易UTXO,共分为三类(目标值为本次交易的输出值)
(1)该交易金额小于目标值,保存交易到小交易列表vValue
(2)该交易金额大于目标值且小于已有最大交易值,保存交易到最大交易信息pcoinLowestLarger
(3)该交易金额等于目标值,直接返回该交易

 
 
  1. if (n < nTargetValue) 如果该记录的金额小于目标值
  2. {
  3. vValue.push_back(make_pair(n, pcoin)); 保存为候选对象
  4. nTotalLower += n; 总金额累加
  5. }
  6. else if (n == nTargetValue) 如果该记录的金额等于目标值
  7. {
  8. setCoinsRet.insert(pcoin); 保存并直接返回
  9. return true;
  10. }
  11. else if (n < nLowestLarger) 如果该记录金额大于目标值,且该值小于已登记的值
  12. {
  13. nLowestLarger = n; 替换金额
  14. pcoinLowestLarger = pcoin; 替换记录对象
  15. }

选择最终交易

(1)所有小交易列表的值小于目标值,如果最大交易值pcoinLowestLarger 存在则返回,否则返回false

 
 
  1. if (nTotalLower < nTargetValue) 如果小于的目标值的和值小于目标值
  2. {
  3. if (pcoinLowestLarger == NULL) 没有找到大于目标值的对象,则查找失败
  4. return false;
  5. setCoinsRet.insert(pcoinLowestLarger); 将大于目标值的对象返回
  6. return true;
  7. }

(2)所有小交易列表的值大于目标值
采用随机逼近算法查找最合适的交易组合。(最合适交易组合指的是这些交易组合值与目标输出值最接近)

 
 
  1. // Solve subset sum by stochastic approximation
  2. sort(vValue.rbegin(), vValue.rend()); 对小于目标值的所有key进行排序
  3. vector<char> vfIncluded; 排除标志符
  4. vector<char> vfBest(vValue.size(), true); 最优记录标志符
  5. int64 nBest = nTotalLower; 最优值,该值将动态调整
  6. for (int nRep = 0; nRep < 1000 && nBest != nTargetValue; nRep++) 操作1000次,如果最优值等于目标值或者1000次结束,则退出循环
  7. {
  8. vfIncluded.assign(vValue.size(), false); 排除标志符设为false
  9. int64 nTotal = 0; 总数为0
  10. bool fReachedTarget = false; 找到合适对象设为false
  11. for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) 进行两次操作,当两次操作完成或者找到合适对象时退出循环
  12. {
  13. for (int i = 0; i < vValue.size(); i++) 遍历所有小于目标值的对象
  14. {
  15. if (nPass == 0 ? rand() % 2 : !vfIncluded[i]) 第一次操作采取随机抽取,第二次操作针对第一次没有随机抽取到的对象操作
  16. {
  17. nTotal += vValue[i].first; 累加值
  18. vfIncluded[i] = true; 标识为已使用
  19. if (nTotal >= nTargetValue) 如果总数大于目标值
  20. {
  21. fReachedTarget = true; 发现目标
  22. if (nTotal < nBest) 该目标比以往发现的目标好
  23. {
  24. nBest = nTotal; 替换为最好目标
  25. vfBest = vfIncluded; 保存相应的对象
  26. }
  27. nTotal -= vValue[i].first; 放弃该对象,寻找下一对象
  28. vfIncluded[i] = false; 该对象值为false,便于第二次随机抽取使用
  29. }
  30. }
  31. }
  32. }
  33. }
  34. // If the next larger is still closer, return it
  35. if (pcoinLowestLarger && nLowestLarger - nTargetValue <= nBest - nTargetValue) 最大值对象比累加对象要好
  36. setCoinsRet.insert(pcoinLowestLarger); 返回最大对象
  37. else
  38. {
  39. for (int i = 0; i < vValue.size(); i++) 循环候选对象
  40. if (vfBest[i])
  41. setCoinsRet.insert(vValue[i].second); 取出合适对象
  42. //// debug print
  43. printf("SelectCoins() best subset: ");
  44. for (int i = 0; i < vValue.size(); i++)
  45. if (vfBest[i])
  46. printf("%s ", FormatMoney(vValue[i].first).c_str());
  47. printf("total %s\n", FormatMoney(nBest).c_str());
  48. }
  49. return true;

上一篇:比特币源码解读之交易发起
下一篇:比特币源码解读之区块确认

版权声明:B链网原创,严禁修改。转载请注明作者和原文链接

作者:雨后的蚊子

原文链接:http://www.360bchain.com/article/123.html

猜你喜欢

转载自blog.csdn.net/Tostick/article/details/80398181