Post Jobs

华泰证券张铭锋,基于Java语言构建区块链

澳门新萄京最大平台 1

据上海证券报消息,华泰证券资产管理信息技术部负责人张铭锋日前表示,区块链的共识机制能确保信息在传输过程中的透明性、完整性和及时性,防止传统数据共享模式下的数据篡改。

澳门新萄京最大平台 1

那些参与方众多、缺乏集中交易场所及信息共享机制的交易品种,如票据业务、衍生品交易等,都适用区块链。

最终内容请以原文为准:

在这一系列文章的最开始部分,我们提到过区块链是一个分布式的数据库。那时候,我们决定跳过”分布式”这一环节,并且聚焦于”数据存储”这一环节。到目前为止,我们几乎实现了区块链的所有组成部分。在本篇文章中,我们将会涉及一些在前面的文章中所忽略的一些机制,并且在下一篇文章中我们将开始研究区块链的分布式特性。

前面各个部分内容:

  1. 基本原型
  2. 工作量证明
  3. 持久化 & 命令行
  4. 交易
  5. 地址

在 持久化 & 命令行
这篇文章中,我们研究了比特币核心存储区块的方式。当中我们提到过与区块相关的数据存储在
blocks 这个数据桶中,而交易数据则存储在 chainstate
这个数据桶中,让我们来回忆一下,chainstate 数据桶的数据结构:

  • 澳门新萄京最大平台,’c’ + 32-byte transaction hash -> unspent transaction output
    record for that transaction

    某笔交易的UTXO记录

  • ‘B’ -> 32-byte block hash: the block hash up to which the
    database represents the unspent transaction outputs

    数据库所表示的UTXO的区块Hash

从那篇文章开始,我们已经实现了比特币的交易机制,但是我们还没有用到
chainstate
数据桶去存储我们的交易输出。所以,这将是我们现在要去做的事情。

chainstate 不会去存储交易数据。相反,它存储的是 UTXO
集,也就是未被花费的交易输出集合。除此之外,它还存储了”数据库所表示的UTXO的区块Hash”,我们这里先暂且忽略这一点,因为我们还没有用到区块高度(这一点我们会在后面的文章进行实现)。

那么,我们为什么需要 UTXO 池呢?

一起来看一下我们前面实现的 findUnspentTransactions 方法:

 /** * 查找钱包地址对应的所有未花费的交易 * * @param pubKeyHash 钱包公钥Hash * @return */ private Transaction[] findUnspentTransactions(byte[] pubKeyHash) throws Exception { Map<String, int[]> allSpentTXOs = this.getAllSpentTXOs(pubKeyHash); Transaction[] unspentTxs = {}; // 再次遍历所有区块中的交易输出 for (BlockchainIterator blockchainIterator = this.getBlockchainIterator(); blockchainIterator.hashNext { Block block = blockchainIterator.next(); for (Transaction transaction : block.getTransactions { String txId = Hex.encodeHexString(transaction.getTxId; int[] spentOutIndexArray = allSpentTXOs.get; for (int outIndex = 0; outIndex < transaction.getOutputs().length; outIndex++) { if (spentOutIndexArray != null && ArrayUtils.contains(spentOutIndexArray, outIndex)) { continue; } // 保存不存在 allSpentTXOs 中的交易 if (transaction.getOutputs()[outIndex].isLockedWithKey(pubKeyHash)) { unspentTxs = ArrayUtils.add(unspentTxs, transaction); } } } } return unspentTxs; }

该方法是用来查找钱包地址对应的包含未花费交易输出的交易信息。由于交易信息是存储在区块当中,所以我们现有的做法是遍历区块链中的每个区块,然后遍历每个区块中的交易信息,再然后遍历每个交易中的交易输出,并检查交易输出是否被相应的钱包地址所锁定,效率非常低下。截止2018年3月29号,比特币中有
515698 个区块,并且这些数据占据了140+Gb
的磁盘空间。这也就意味着一个人必须运行全节点(下载所有的区块数据)才能验证交易信息。此外,验证交易信息需要遍历所有的区块。

针对这个问题的解决办法是需要有一个存储了所有UTXOs的索引,这就是 UTXOs
池所要做的事情:UTXOs池其实是一个缓存空间,它所缓存的数据需要从构建区块链中所有的交易数据中获得(通过遍历所有的区块链,不过这个构建操作只需要执行一次即可),并且它后续还会用于钱包余额的计算以及新的交易数据的验证。截止到2017年9月,UTXOs池大约为
2.7Gb。

好了,让我们来想一下,为了实现 UTXOs
池我们需要做哪些事情。当前,有下列方法被用于查找交易信息:

  1. Blockchain.getAllSpentTXOs ——
    查询所有已被花费的交易输出。它需要遍历区块链中所有区块中交易信息。

  2. Blockchain.findUnspentTransactions ——
    查询包含未被花费的交易输出的交易信息。它也需要遍历区块链中所有区块中交易信息。

  3. Blockchain.findSpendableOutputs ——
    该方法用于新的交易创建之时。它需要找到足够多的交易输出,以满足所需支付的金额。需要调用
    Blockchain.findUnspentTransactions 方法。

  4. Blockchain.findUTXO ——
    查询钱包地址所对应的所有未花费交易输出,然后用于计算钱包余额。需要调用

    Blockchain.findUnspentTransactions 方法。

  5. Blockchain.findTransaction ——
    通过交易ID查询交易信息。它需要遍历所有的区块直到找到交易信息为止。

如你所见,上面这些方法都需要去遍历数据库中的所有区块。由于UTXOs池只存储未被花费的交易输出,而不会存储所有的交易信息,因此我们不会对有
Blockchain.findTransaction 进行优化。

那么,我们需要下列这些方法:

  1. Blockchain.findUTXO ——
    通过遍历所有的区块来找到所有未被花费的交易输出.
  2. UTXOSet.reindex —— 调用上面 findUTXO
    方法,然后将查询结果存储在数据库中。也即需要进行缓存的地方。
  3. UTXOSet.findSpendableOutputs —— 与
    Blockchain.findSpendableOutputs 类似,区别在于会使用 UTXO 池。
  4. UTXOSet.findUTXO —— 与Blockchain.findUTXO
    类似,区别在于会使用 UTXO 池。
  5. Blockchain.findTransaction —— 逻辑保持不变。

这样,两个使用最频繁的方法将从现在开始使用缓存!让我们开始编码吧!

定义 UTXOSet

@NoArgsConstructor@AllArgsConstructor@Slf4jpublic class UTXOSet { private Blockchain blockchain;}

重建 UTXO 池索引:

public class UTXOSet { ... /** * 重建 UTXO 池索引 */ @Synchronized public void reIndex() { log.info("Start to reIndex UTXO set !"); RocksDBUtils.getInstance().cleanChainStateBucket(); Map<String, TXOutput[]> allUTXOs = blockchain.findAllUTXOs(); for (Map.Entry<String, TXOutput[]> entry : allUTXOs.entrySet { RocksDBUtils.getInstance().putUTXOs(entry.getKey(), entry.getValue; } log.info("ReIndex UTXO set finished ! "); } ...} 

此方法用于初始化 UTXOSet。首先,需要清空 chainstate
数据桶,然后查询所有未被花费的交易输出,并将它们保存到 chainstate
数据桶中。

实现 findSpendableOutputs 方法,供 Transation.newUTXOTransaction
调用

public class UTXOSet { ... /** * 寻找能够花费的交易 * * @param pubKeyHash 钱包公钥Hash * @param amount 花费金额 */ public SpendableOutputResult findSpendableOutputs(byte[] pubKeyHash, int amount) { Map<String, int[]> unspentOuts = Maps.newHashMap(); int accumulated = 0; Map<String, byte[]> chainstateBucket = RocksDBUtils.getInstance().getChainstateBucket(); for (Map.Entry<String, byte[]> entry : chainstateBucket.entrySet { String txId = entry.getKey(); TXOutput[] txOutputs = (TXOutput[]) SerializeUtils.deserialize(entry.getValue; for (int outId = 0; outId < txOutputs.length; outId++) { TXOutput txOutput = txOutputs[outId]; if (txOutput.isLockedWithKey(pubKeyHash) && accumulated < amount) { accumulated += txOutput.getValue(); int[] outIds = unspentOuts.get; if (outIds == null) { outIds = new int[]{outId}; } else { outIds = ArrayUtils.add(outIds, outId); } unspentOuts.put(txId, outIds); if (accumulated >= amount) { break; } } } } return new SpendableOutputResult(accumulated, unspentOuts); } ... } 

实现 findUTXOs 接口,供 CLI.getBalance 调用:

public class UTXOSet { ... /** * 查找钱包地址对应的所有UTXO * * @param pubKeyHash 钱包公钥Hash * @return */ public TXOutput[] findUTXOs(byte[] pubKeyHash) { TXOutput[] utxos = {}; Map<String, byte[]> chainstateBucket = RocksDBUtils.getInstance().getChainstateBucket(); if (chainstateBucket.isEmpty { return utxos; } for (byte[] value : chainstateBucket.values { TXOutput[] txOutputs = (TXOutput[]) SerializeUtils.deserialize; for (TXOutput txOutput : txOutputs) { if (txOutput.isLockedWithKey(pubKeyHash)) { utxos = ArrayUtils.add(utxos, txOutput); } } } return utxos; } ... } 

以上这些方法都是先前 Blockchain
中相应方法的微调版,先前的方法将不再使用。

有了UTXO池之后,意味着我们的交易数据分开存储到了两个不同的数据桶中:交易数据存储到了
block 数据桶中,而UTXO存储到了 chainstate
数据桶中。这就需要一种同步机制来保证每当一个新的区块产生时,UTXO池能够及时同步最新区块中的交易数据,毕竟我们不想频地进行
reIndex 。因此,我们需要如下方法:

更新UTXO池:

public class UTXOSet { ... /** * 更新UTXO池 * <p> * 当一个新的区块产生时,需要去做两件事情: * 1)从UTXO池中移除花费掉了的交易输出; * 2)保存新的未花费交易输出; * * @param tipBlock 最新的区块 */ @Synchronized public void update(Block tipBlock) { if (tipBlock == null) { log.error("Fail to update UTXO set ! tipBlock is null !"); throw new RuntimeException("Fail to update UTXO set ! "); } for (Transaction transaction : tipBlock.getTransactions { // 根据交易输入排查出剩余未被使用的交易输出 if (!transaction.isCoinbase { for (TXInput txInput : transaction.getInputs { // 余下未被使用的交易输出 TXOutput[] remainderUTXOs = {}; String txId = Hex.encodeHexString(txInput.getTxId; TXOutput[] txOutputs = RocksDBUtils.getInstance().getUTXOs; if (txOutputs == null) { continue; } for (int outIndex = 0; outIndex < txOutputs.length; outIndex++) { if (outIndex != txInput.getTxOutputIndex { remainderUTXOs = ArrayUtils.add(remainderUTXOs, txOutputs[outIndex]); } } // 没有剩余则删除,否则更新 if (remainderUTXOs.length == 0) { RocksDBUtils.getInstance().deleteUTXOs; } else { RocksDBUtils.getInstance().putUTXOs(txId, remainderUTXOs); } } } // 新的交易输出保存到DB中 TXOutput[] txOutputs = transaction.getOutputs(); String txId = Hex.encodeHexString(transaction.getTxId; RocksDBUtils.getInstance().putUTXOs(txId, txOutputs); } } ... } 

让我们将 UTXOSet 用到它们所需之处去:

public class CLI { ... /** * 创建区块链 * * @param address */ private void createBlockchain(String address) { Blockchain blockchain = Blockchain.createBlockchain; UTXOSet utxoSet = new UTXOSet(blockchain); utxoSet.reIndex(); log.info("Done ! "); } ... } 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

相关文章

网站地图xml地图