成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

基于Java語言構(gòu)建區(qū)塊鏈(四)—— 交易(UTXO)

Vultr / 2729人閱讀

摘要:交易這一環(huán)節(jié)是整個比特幣系統(tǒng)當(dāng)中最為關(guān)鍵的一環(huán),并且區(qū)塊鏈唯一的目的就是通過安全的可信的方式來存儲交易信息,防止它們創(chuàng)建之后被人惡意篡改。在比特幣中,交易輸出先于交易輸入出現(xiàn)。

最終內(nèi)容請以原文為準(zhǔn): https://wangwei.one/posts/9cf...
引言

上一篇 文章,我們實現(xiàn)了區(qū)塊數(shù)據(jù)的持久化,本篇開始交易環(huán)節(jié)的實現(xiàn)。交易這一環(huán)節(jié)是整個比特幣系統(tǒng)當(dāng)中最為關(guān)鍵的一環(huán),并且區(qū)塊鏈唯一的目的就是通過安全的、可信的方式來存儲交易信息,防止它們創(chuàng)建之后被人惡意篡改。今天我們開始實現(xiàn)交易這一環(huán)節(jié),但由于這是一個很大的話題,所以我們分為兩部分:第一部分我們將實現(xiàn)區(qū)塊鏈交易的基本機制,到第二部分,我們再來研究它的細(xì)節(jié)。

比特幣交易

如果你開發(fā)過Web應(yīng)用程序,為了實現(xiàn)支付系統(tǒng),你可能會在數(shù)據(jù)庫中創(chuàng)建一些數(shù)據(jù)庫表:賬戶交易記錄。賬戶用于存儲用戶的個人信息以及賬戶余額等信息,交易記錄用于存儲資金從一個賬戶轉(zhuǎn)移到另一個賬戶的記錄。但是在比特幣中,支付系統(tǒng)是以一種完全不一樣的方式實現(xiàn)的,在這里:

沒有賬戶

沒有余額

沒有地址

沒有 Coins(幣)

沒有發(fā)送者和接受者

由于區(qū)塊鏈?zhǔn)且粋€公開的數(shù)據(jù)庫,我們不希望存儲有關(guān)錢包所有者的敏感信息。Coins 不會匯總到錢包中。交易不會將資金從一個地址轉(zhuǎn)移到另一個地址。沒有可保存帳戶余額的字段或?qū)傩?。只有交易信息。那比特幣的交易信息里面到底存儲的是什么呢?/p> 交易組成

一筆比特幣的交易由 交易輸入交易輸出 組成,數(shù)據(jù)結(jié)構(gòu)如下:

/**
 * 交易
 *
 * @author wangwei
 * @date 2017/03/04
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Transaction {

    /**
     * 交易的Hash
     */
    private byte[] txId;
    /**
     * 交易輸入
     */
    private TXInput[] inputs;
    /**
     * 交易輸出
     */
    private TXOutput[] outputs;

    
}

一筆交易的 交易輸入 其實是指向上一筆交易的交易輸出 (這個后面詳細(xì)說明)。我們錢包里面的 Coin(幣)實際是存儲在這些 交易輸出 里面。下圖表示了區(qū)塊鏈交易系統(tǒng)里面各個交易相互引用的關(guān)系:

注意:

有些 交易輸出 并不是由 交易輸入 產(chǎn)生,而是憑空產(chǎn)生的(后面會詳細(xì)介紹)。

但,交易輸入 必須指向某個 交易輸出,它不能憑空產(chǎn)生。

在一筆交易里面,交易輸入 可能會來自多筆交易所產(chǎn)生的 交易輸出

在整篇文章中,我們將使用諸如“錢”,“硬幣”,“花費”,“發(fā)送”,“賬戶”等詞語。但比特幣中沒有這樣的概念,在比特幣交易中,交易信息是由 鎖定腳本 鎖定一個數(shù)值,并且只能被所有者的 解鎖腳本 解鎖。(解鈴還須系鈴人)

交易輸出

讓我們先從交易輸出開始,他的數(shù)據(jù)結(jié)構(gòu)如下:

/**
 * 交易輸出
 *
 * @author wangwei
 * @date 2017/03/04
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TXOutput {

    /**
     * 數(shù)值
     */
    private int value;
    /**
     * 鎖定腳本
     */
    private String scriptPubKey;
    
    
}

實際上,它表示的是能夠存儲 "coins(幣)"的交易輸出(注意 value 字段)。并且這里所謂的 value 實際上是由存儲在 ScriptPubKey (鎖定腳本)中的一個puzzle(難題) 所鎖定。在內(nèi)部,比特幣使用稱為腳本的腳本語言,用于定義輸出鎖定和解鎖邏輯。這個語言很原始(這是故意的,以避免可能的黑客和濫用),但我們不會詳細(xì)討論它。 你可以在這里找到它的詳細(xì)解釋。here

在比特幣中,value 字段存儲著 satoshis 的任意倍的數(shù)值,而不是BTC的數(shù)量。satoshis 是比特幣的百萬分之一(0.00000001 BTC),因此這是比特幣中最小的貨幣單位(如1美分)。

satoshis:聰

鎖定腳本是一個放在一個輸出值上的“障礙”,同時它明確了今后花費這筆輸出的條件。由于鎖定腳本往往含有一個公鑰(即比特幣地址),在歷史上它曾被稱作一個腳本公鑰代碼。在大多數(shù)比特幣應(yīng)用源代碼中,腳本公鑰代碼便是我們所說的鎖定腳本。

由于我們還沒有實現(xiàn)錢包地址的邏輯,所以這里先暫且忽略鎖定腳本相關(guān)的邏輯。ScriptPubKey 將會存儲任意的字符串(用戶定義的錢包地址)

順便說一句,擁有這樣的腳本語言意味著比特幣也可以用作智能合約平臺。

關(guān)于 交易輸出 的一個重要的事情是它們是不可分割的,這意味著你不能將它所存儲的數(shù)值拆開來使用。當(dāng)這個交易輸出在新的交易中被交易輸入所引用時,它將作為一個整體被花費掉。 如果其值大于所需值,那么剩余的部分則會作為零錢返回給付款方。 這與真實世界的情況類似,例如,您支付5美元的鈔票用于購買1美元的東西,那么你將會得到4美元的零錢。

交易輸入
/**
 * 交易輸入
 *
 * @author wangwei
 * @date 2017/03/04
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TXInput {

    /**
     * 交易Id的hash值
     */
    private byte[] txId;
    /**
     * 交易輸出索引
     */
    private int txOutputIndex;
    /**
     * 解鎖腳本
     */
    private String scriptSig;

    
}

前面提到過,一個交易輸入指向的是某一筆交易的交易輸出:

txId 存儲的是某筆交易的ID值

txOutputIndex 存儲的是交易中這個交易輸出的索引位置(因為一筆交易可能包含多個交易輸出)

scriptSig 主要是提供用于交易輸出中 ScriptPubKey 所需的驗證數(shù)據(jù)。

如果這個數(shù)據(jù)被驗證正確,那么相應(yīng)的交易輸出將被解鎖,并且其中的 value 能夠生成新的交易輸出;

如果不正確,那么相應(yīng)的交易輸出將不能被交易輸入所引用;

通過鎖定腳本與解鎖腳本這種機制,保證了某個用戶不能花費屬于他人的Coins。

同樣,由于我們尚未實現(xiàn)錢包地址功能,ScriptSig 將會存儲任意的用戶所定義的錢包地址。我們將會在下一章節(jié)實現(xiàn)公鑰和數(shù)字簽名驗證。

說了這么多,我們來總結(jié)一下。交易輸出是"Coins"實際存儲的地方。每一個交易輸出都帶有一個鎖定腳本,它決定了解鎖的邏輯。每一筆新的交易必須至少有一個交易輸入與交易輸出。一筆交易的交易輸入指向前一筆交易的交易輸出,并且提供用于鎖定腳本解鎖需要的數(shù)據(jù)(ScriptSig 字段),然后利用交易輸出中的 value 去創(chuàng)建新的交易輸出。

注意,這段話的原文如下,但是里面有表述錯誤的地方,交易輸出帶有的是鎖定腳本,而不是解鎖腳本。

Let’s sum it up. Outputs are where “coins” are stored. Each output comes with an unlocking script, which determines the logic of unlocking the output. Every new transaction must have at least one input and output. An input references an output from a previous transaction and provides data (the ScriptSig field) that is used in the output’s unlocking script to unlock it and use its value to create new outputs.

那到底是先有交易輸入還是先有交易輸出呢?

雞與蛋的問題

在比特幣中,雞蛋先于雞出現(xiàn)。交易輸入源自于交易輸出的邏輯是典型的"先有雞還是先有蛋"的問題:交易輸入產(chǎn)生交易輸出,交易輸出又會被交易輸入所引用。在比特幣中,交易輸出先于交易輸入出現(xiàn)。

當(dāng)?shù)V工開始開采區(qū)塊時,區(qū)塊中會被添加一個 coinbase 交易。coinbase 交易是一種特殊的交易,它不需要以前已經(jīng)存在的交易輸出。它會憑空創(chuàng)建出交易輸出(i.e: Coins)。也即,雞蛋的出現(xiàn)并不需要母雞,這筆交易是作為礦工成功挖出新的區(qū)塊后的一筆獎勵。

正如你所知道的那樣,在區(qū)塊鏈的最前端,即第一個區(qū)塊,有一個創(chuàng)世區(qū)塊。他產(chǎn)生了區(qū)塊鏈中有史以來的第一個交易輸出,并且由于沒有前一筆交易,也就沒有相應(yīng)的輸出,因此不需要前一筆交易的交易輸出。

讓我們來創(chuàng)建 coinbase 交易:

/**
 * 創(chuàng)建CoinBase交易
 *
 * @param to   收賬的錢包地址
 * @param data 解鎖腳本數(shù)據(jù)
 * @return
 */
public Transaction newCoinbaseTX(String to, String data) {
    if (StringUtils.isBlank(data)) {
        data = String.format("Reward to "%s"", to);
    }
    // 創(chuàng)建交易輸入
    TXInput txInput = new TXInput(new byte[]{}, -1, data);
    // 創(chuàng)建交易輸出
    TXOutput txOutput = new TXOutput(SUBSIDY, to);
    // 創(chuàng)建交易
    Transaction tx = new Transaction(null, new TXInput[]{txInput}, new TXOutput[]{txOutput});
    // 設(shè)置交易ID
    tx.setTxId();
    return tx;
}

coinbase交易只有一個交易輸入。在我們的代碼實現(xiàn)中,txId 是空數(shù)組,txOutputIndex 設(shè)置為了 -1。另外,coinbase交易不會在 ScriptSig 字段上存儲解鎖腳本,相反,存了一個任意的數(shù)據(jù)。

在比特幣中,第一個 coinbase 交易報刊了如下的信息:"The Times 03/Jan/2009 Chancellor on brink of second bailout for banks". 點擊查看

SUBSIDY 是挖礦獎勵數(shù)量。在比特幣中,這個獎勵數(shù)量沒有存儲在任何地方,而是依據(jù)現(xiàn)有區(qū)塊的總數(shù)進行計算而得到:區(qū)塊總數(shù) 除以 210000。開采創(chuàng)世區(qū)塊得到的獎勵為50BTC,每過 210000 個區(qū)塊,獎勵會減半。在我們的實現(xiàn)中,我們暫且將挖礦獎勵設(shè)置為常數(shù)。(至少目前是這樣)

在區(qū)塊鏈中存儲交易信息

從現(xiàn)在開始,每一個區(qū)塊必須存儲至少一個交易信息,并且盡可能地避免在沒有交易數(shù)據(jù)的情況下進行挖礦。這意味著我們必須移除 Block 對象中的 date 字段,取而代之的是 transactions

/**
 * 區(qū)塊
 *
 * @author wangwei
 * @date 2018/02/02
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Block {

    /**
     * 區(qū)塊hash值
     */
    private String hash;
    /**
     * 前一個區(qū)塊的hash值
     */
    private String previousHash;
    /**
     * 交易信息
     */
    private Transaction[] transactions;
    /**
     * 區(qū)塊創(chuàng)建時間(單位:秒)
     */
    private long timeStamp;

}

相應(yīng)地,newGenesisBlocknewBlock 也都需要做改變:

/**
 * 

創(chuàng)建創(chuàng)世區(qū)塊

* * @param coinbase * @return */ public static Block newGenesisBlock(Transaction coinbase) { return Block.newBlock("", new Transaction[]{coinbase}); } /** *

創(chuàng)建新區(qū)塊

* * @param previousHash * @param transactions * @return */ public static Block newBlock(String previousHash, Transaction[] transactions) { Block block = new Block("", previousHash, transactions, Instant.now().getEpochSecond(), 0); ProofOfWork pow = ProofOfWork.newProofOfWork(block); PowResult powResult = pow.run(); block.setHash(powResult.getHash()); block.setNonce(powResult.getNonce()); return block; }

接下來,修改 newBlockchain 方法:

/**
  * 

創(chuàng)建區(qū)塊鏈

* * @param address 錢包地址 * @return */ public static Blockchain newBlockchain(String address) throws Exception { String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash(); if (StringUtils.isBlank(lastBlockHash)) { // 創(chuàng)建 coinBase 交易 Transaction coinbaseTX = Transaction.newCoinbaseTX(address, ""); Block genesisBlock = Block.newGenesisBlock(coinbaseTX); lastBlockHash = genesisBlock.getHash(); RocksDBUtils.getInstance().putBlock(genesisBlock); RocksDBUtils.getInstance().putLastBlockHash(lastBlockHash); } return new Blockchain(lastBlockHash); }

現(xiàn)在,代碼有錢包地址的接口,將會收到開采創(chuàng)世區(qū)塊的獎勵。

工作量證明(Pow)

Pow算法必須將存儲在區(qū)塊中的交易信息考慮在內(nèi),以保存交易信息存儲的一致性和可靠性。因此,我們必須修改 ProofOfWork.prepareData 接口代碼邏輯:

/**
 * 準(zhǔn)備數(shù)據(jù)
 * 

* 注意:在準(zhǔn)備區(qū)塊數(shù)據(jù)時,一定要從原始數(shù)據(jù)類型轉(zhuǎn)化為byte[],不能直接從字符串進行轉(zhuǎn)換 * @param nonce * @return */ private String prepareData(long nonce) { byte[] prevBlockHashBytes = {}; if (StringUtils.isNoneBlank(this.getBlock().getPrevBlockHash())) { prevBlockHashBytes = new BigInteger(this.getBlock().getPrevBlockHash(), 16).toByteArray(); } return ByteUtils.merge( prevBlockHashBytes, this.getBlock().hashTransaction(), ByteUtils.toBytes(this.getBlock().getTimeStamp()), ByteUtils.toBytes(TARGET_BITS), ByteUtils.toBytes(nonce) ); }

其中 hashTransaction 代碼如下:

/**
 * 對區(qū)塊中的交易信息進行Hash計算
 *
 * @return
 */
public byte[] hashTransaction() {
   byte[][] txIdArrays = new byte[this.getTransactions().length][];
   for (int i = 0; i < this.getTransactions().length; i++) {
       txIdArrays[i] = this.getTransactions()[i].getTxId();
   }
   return DigestUtils.sha256(ByteUtils.merge(txIds));
}

同樣,我們使用哈希值來作為數(shù)據(jù)的唯一標(biāo)識。我們希望區(qū)塊中的所有交易數(shù)據(jù)都能通過一個哈希值來定義它的唯一標(biāo)識。為了達到這個目的,我們計算了每一個交易的唯一哈希值,然后將他們串聯(lián)起來,再對這個串聯(lián)后的組合進行哈希值計算。

比特幣使用更復(fù)雜的技術(shù):它將所有包含在塊中的交易表示為 Merkle樹 ,并在Proof-of-Work系統(tǒng)中使用該樹的根散列。 這種方法只需要跟節(jié)點的散列值就可以快速檢查塊是否包含某筆交易,而無需下載所有交易。
UTXO(未花費交易輸出)
UTXO:unspend transaction output.(未被花費的交易輸出)

在比特幣的世界里既沒有賬戶,也沒有余額,只有分散到區(qū)塊鏈里的UTXO.

UTXO 是理解比特幣交易原理的關(guān)鍵所在,我們先來看一段場景:

場景:假設(shè)你過去分別向A、B、C這三個比特幣用戶購買了BTC,從A手中購買了3.5個BTC,從B手中購買了4.5個BTC,從C手中購買了2個BTC,現(xiàn)在你的比特幣錢包里面恰好剩余10個BTC。

問題:這個10個BTC是真正的10個BTC嗎?其實不是,這句話可能聽起來有點怪。(什么!我錢包里面的BTC不是真正的BTC,你不要嚇我……)

解釋:前面提到過在比特幣的交易系統(tǒng)當(dāng)中,并不存在賬戶、余額這些概念,所以,你的錢包里面的10個BTC,并不是說錢包余額為10個BTC。而是說,這10個BTC其實是由你的比特幣地址(錢包地址|公鑰)鎖定了的散落在各個區(qū)塊和各個交易里面的UTXO的總和。

UTXO 是比特幣交易的基本單位,每筆交易都會產(chǎn)生UTXO,一個UTXO可以是一“聰”的任意倍。給某人發(fā)送比特幣實際上是創(chuàng)造新的UTXO,綁定到那個人的錢包地址,并且能被他用于新的支付。

一般的比特幣交易由 交易輸入交易輸出 兩部分組成。A向你支付3.5個BTC這筆交易,實際上產(chǎn)生了一個新的UTXO,這個新的UTXO 等于 3.5個BTC(3.5億聰),并且鎖定到了你的比特幣錢包地址上。

假如你要給你女(男)朋友轉(zhuǎn) 1.5 BTC,那么你的錢包會從可用的UTXO中選取一個或多個可用的個體來拼湊出一個大于或等于一筆交易所需的比特幣量。比如在這個假設(shè)場景里面,你的錢包會選取你和C的交易中的UTXO作為 交易輸入,input = 2BTC,這里會生成兩個新的交易輸出,一個輸出(UTXO = 1.5 BTC)會被綁定到你女(男)朋友的錢包地址上,另一個輸出(UTXO = 0.5 BTC)會作為找零,重新綁定到你的錢包地址上。

有關(guān)比特幣交易這部分更詳細(xì)的內(nèi)容,請查看:《精通比特幣(第二版)》第6章 —— 交易

我們需要找到所有未花費的交易輸出(UTXO)。Unspent(未花費) 意味著這些交易輸出從未被交易輸入所指向。這前面的圖片中,UTXO如下:

tx0, output 1;

tx1, output 0;

tx3, output 0;

tx4, output 0.

當(dāng)然,當(dāng)我們檢查余額時,我不需要區(qū)塊鏈中所有的UTXO,我只需要能被我們解鎖的UTXO(當(dāng)前,我們還沒有實現(xiàn)密鑰對,而是替代為用戶自定義的錢包地址)。首先,我們在交易輸入與交易輸出上定義鎖定-解鎖的方法:

交易輸入:

public class TXInput {
      
    ...
     
    /**
     * 判斷解鎖數(shù)據(jù)是否能夠解鎖交易輸出
     *
     * @param unlockingData
     * @return
     */
    public boolean canUnlockOutputWith(String unlockingData) {
        return this.getScriptSig().endsWith(unlockingData);
    }
}

交易輸出:

public class TXOutput {
    
    ...
        
    /**
     * 判斷解鎖數(shù)據(jù)是否能夠解鎖交易輸出
     *
     * @param unlockingData
     * @return
     */
    public boolean canBeUnlockedWith(String unlockingData) {
        return this.getScriptPubKey().endsWith(unlockingData);
    }
}

這里我們暫時用 unlockingData 來與腳本字段進行比較。我們會在后面的文章中來對這部分內(nèi)容進行優(yōu)化,我們將會基于私鑰來實現(xiàn)用戶的錢包地址。

下一步,查詢所有與錢包地址綁定的包含UTXO的交易信息,有點復(fù)雜(本篇先這樣實現(xiàn),后面我們做一個與錢包地址映射的UTXO池來進行優(yōu)化):

從與錢包地址對應(yīng)的交易輸入中查詢出所有已被花費了的交易輸出

再來排除,尋找包含未被花費的交易輸出的交易

public class Blockchain {

    ...

    /**
     * 查找錢包地址對應(yīng)的所有未花費的交易
     *
     * @param address 錢包地址
     * @return
     */
    private Transaction[] findUnspentTransactions(String address) throws Exception {
        Map allSpentTXOs = this.getAllSpentTXOs(address);
        Transaction[] unspentTxs = {};

        // 再次遍歷所有區(qū)塊中的交易輸出
        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(txId);

                for (int outIndex = 0; outIndex < transaction.getOutputs().length; outIndex++) {
                    if (spentOutIndexArray != null && ArrayUtils.contains(spentOutIndexArray, outIndex)) {
                        continue;
                    }

                    // 保存不存在 allSpentTXOs 中的交易
                    if (transaction.getOutputs()[outIndex].canBeUnlockedWith(address)) {
                        unspentTxs = ArrayUtils.add(unspentTxs, transaction);
                    }
                }
            }
        }
        return unspentTxs;
    }


    /**
     * 從交易輸入中查詢區(qū)塊鏈中所有已被花費了的交易輸出
     *
     * @param address 錢包地址
     * @return 交易ID以及對應(yīng)的交易輸出下標(biāo)地址
     * @throws Exception
     */
    private Map getAllSpentTXOs(String address) throws Exception {
        // 定義TxId ——> spentOutIndex[],存儲交易ID與已被花費的交易輸出數(shù)組索引值
        Map spentTXOs = new HashMap<>();
        for (BlockchainIterator blockchainIterator = this.getBlockchainIterator(); blockchainIterator.hashNext(); ) {
            Block block = blockchainIterator.next();

            for (Transaction transaction : block.getTransactions()) {
                // 如果是 coinbase 交易,直接跳過,因為它不存在引用前一個區(qū)塊的交易輸出
                if (transaction.isCoinbase()) {
                    continue;
                }
                for (TXInput txInput : transaction.getInputs()) {
                    if (txInput.canUnlockOutputWith(address)) {
                        String inTxId = Hex.encodeHexString(txInput.getTxId());
                        int[] spentOutIndexArray = spentTXOs.get(inTxId);
                        if (spentOutIndexArray == null) {
                            spentTXOs.put(inTxId, new int[]{txInput.getTxOutputIndex()});
                        } else {
                            spentOutIndexArray = ArrayUtils.add(spentOutIndexArray, txInput.getTxOutputIndex());
                            spentTXOs.put(inTxId, spentOutIndexArray);
                        }
                    }
                }
            }
        }
        return spentTXOs;
    }
    
    ...
}

得到了所有包含UTXO的交易數(shù)據(jù),接下來,我們就可以得到所有UTXO集合了:

public class Blockchain {

   ...

   /**
     * 查找錢包地址對應(yīng)的所有UTXO
     *
     * @param address 錢包地址
     * @return
     */
    public TXOutput[] findUTXO(String address) throws Exception {
        Transaction[] unspentTxs = this.findUnspentTransactions(address);
        TXOutput[] utxos = {};
        if (unspentTxs == null || unspentTxs.length == 0) {
            return utxos;
        }
        for (Transaction tx : unspentTxs) {
            for (TXOutput txOutput : tx.getOutputs()) {
                if (txOutput.canBeUnlockedWith(address)) {
                    utxos = ArrayUtils.add(utxos, txOutput);
                }
            }
        }
        return utxos;
    }
    
    ...
    
}

現(xiàn)在,我們可以實現(xiàn)獲取錢包地址余額的接口了:

public class CLI {

    ...
        
   /**
     * 查詢錢包余額
     *
     * @param address 錢包地址
     */
    private void getBalance(String address) throws Exception {
        Blockchain blockchain = Blockchain.createBlockchain(address);
        TXOutput[] txOutputs = blockchain.findUTXO(address);
        int balance = 0;
        if (txOutputs != null && txOutputs.length > 0) {
            for (TXOutput txOutput : txOutputs) {
                balance += txOutput.getValue();
            }
        }
        System.out.printf("Balance of "%s": %d
", address, balance);
    }
 
    ...
        
}

查詢 wangwei 這個錢包地址的余額:

$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address wangwei

# 輸出
Balance of "wangwei": 10
轉(zhuǎn)賬

現(xiàn)在,我們想要給某人發(fā)送一些幣。因此,我們需要創(chuàng)建一筆新的交易,然后放入?yún)^(qū)塊中,再進行挖礦。到目前為止,我們只是實現(xiàn)了 coinbase 交易,現(xiàn)在我們需要實現(xiàn)常見的創(chuàng)建交易接口:

public class Transaction {
 
   ...
   
   /**
     * 從 from 向  to 支付一定的 amount 的金額
     *
     * @param from       支付錢包地址
     * @param to         收款錢包地址
     * @param amount     交易金額
     * @param blockchain 區(qū)塊鏈
     * @return
     */
    public static Transaction newUTXOTransaction(String from, String to, int amount, Blockchain blockchain) throws Exception {
        SpendableOutputResult result = blockchain.findSpendableOutputs(from, amount);
        int accumulated = result.getAccumulated();
        Map unspentOuts = result.getUnspentOuts();

        if (accumulated < amount) {
            throw new Exception("ERROR: Not enough funds");
        }
        Iterator> iterator = unspentOuts.entrySet().iterator();

        TXInput[] txInputs = {};
        while (iterator.hasNext()) {
            Map.Entry entry = iterator.next();
            String txIdStr = entry.getKey();
            int[] outIdxs = entry.getValue();
            byte[] txId = Hex.decodeHex(txIdStr);
            for (int outIndex : outIdxs) {
                txInputs = ArrayUtils.add(txInputs, new TXInput(txId, outIndex, from));
            }
        }

        TXOutput[] txOutput = {};
        txOutput = ArrayUtils.add(txOutput, new TXOutput(amount, to));
        if (accumulated > amount) {
            txOutput = ArrayUtils.add(txOutput, new TXOutput((accumulated - amount), from));
        }

        Transaction newTx = new Transaction(null, txInputs, txOutput);
        newTx.setTxId();
        return newTx;
    }
    
    ...
    
}    

在創(chuàng)建新的交易輸出之前,我們需要事先找到所有的UTXO,并確保有足夠的金額。這就是 findSpendableOutputs 要干的事情。之后,為每個找到的輸出創(chuàng)建一個引用它的輸入。接下來,我們創(chuàng)建兩個交易輸出:

一個 output 用于鎖定到接收者的錢包地址上。這個是真正被轉(zhuǎn)走的coins;

另一個 output 鎖定到發(fā)送者的錢包地址上。這個就是 找零。只有當(dāng)用于支付的UTXO總和大于要支付的金額時,才會創(chuàng)建這部分的 交易輸出。記?。航灰纵敵鍪?strong>不可分割的

findSpendableOutputs 需要調(diào)用我們之前創(chuàng)建的 findUnspentTransactions 接口:

public class Blockchain {

    ...
    
    /**
     * 尋找能夠花費的交易
     *
     * @param address 錢包地址
     * @param amount  花費金額
     */
    public SpendableOutputResult findSpendableOutputs(String address, int amount) throws Exception {
        Transaction[] unspentTXs = this.findUnspentTransactions(address);
        int accumulated = 0;
        Map unspentOuts = new HashMap<>();
        for (Transaction tx : unspentTXs) {

            String txId = Hex.encodeHexString(tx.getTxId());

            for (int outId = 0; outId < tx.getOutputs().length; outId++) {

                TXOutput txOutput = tx.getOutputs()[outId];

                if (txOutput.canBeUnlockedWith(address) && accumulated < amount) {
                    accumulated += txOutput.getValue();

                    int[] outIds = unspentOuts.get(txId);
                    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);
    }
    
    ...

}

這個方法會遍歷所有的UTXO并統(tǒng)計他們的總額。當(dāng)計算的總額恰好大于或者等于需要轉(zhuǎn)賬的金額時,方法會停止遍歷,然后返回用于支付的總額以及按交易ID分組的交易輸出索引值數(shù)組。我們不想要花更多的錢。

現(xiàn)在,我們可以修改 Block.mineBlock 接口:

public class Block {
   
   ...
   
   /**
     * 打包交易,進行挖礦
     *
     * @param transactions
     */
    public void mineBlock(Transaction[] transactions) throws Exception {
        String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash();
        if (lastBlockHash == null) {
            throw new Exception("ERROR: Fail to get last block hash ! ");
        }
        Block block = Block.newBlock(lastBlockHash, transactions);
        this.addBlock(block);
    }
    
    ...
    
}    

最后,我們來實現(xiàn)轉(zhuǎn)賬的接口:

public class CLI {
   
   ...
  
   /**
     * 轉(zhuǎn)賬
     *
     * @param from
     * @param to
     * @param amount
     */
    private void send(String from, String to, int amount) throws Exception {
        Blockchain blockchain = Blockchain.createBlockchain(from);
        Transaction transaction = Transaction.newUTXOTransaction(from, to, amount, blockchain);
        blockchain.mineBlock(new Transaction[]{transaction});
        RocksDBUtils.getInstance().closeDB();
        System.out.println("Success!");
    }
    
    ...
    
}    

轉(zhuǎn)賬,意味著創(chuàng)建一筆新的交易并且通過挖礦的方式將其存入?yún)^(qū)塊中。但是,比特幣不會像我們這樣做,它會把新的交易記錄先存到內(nèi)存池中,當(dāng)一個礦工準(zhǔn)備去開采一個區(qū)塊時,它會把打包內(nèi)存池中的所有交易信息,并且創(chuàng)建一個候選區(qū)塊。只有當(dāng)這個包含所有交易信息的候選區(qū)塊被成功開采并且被添加到區(qū)塊鏈上時,這些交易信息才算被確認(rèn)。

讓我們來測試一下:

# 先確認(rèn) wangwei 的余額
$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address wangwei
Balance of "wangwei": 10

# 轉(zhuǎn)賬
$ java -jar blockchain-java-jar-with-dependencies.jar send -from wangwei -to Pedro -amount 6
Elapsed Time: 0.828 seconds 
correct hash Hex: 00000c5f50cf72db1f375a5d454f98bc49d07335db921cbef5fa9e58ad34d462 

Success!

# 查詢 wangwei 的余額
$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address wangwei
Balance of "wangwei": 4


# 查詢 Pedro 的余額
$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address Pedro
Balance of "Pedro": 6

贊!現(xiàn)在讓我們來創(chuàng)建更多的交易并且確保從多個交易輸出進行轉(zhuǎn)賬是正常的:

$ java -jar blockchain-java-jar-with-dependencies.jar send -from Pedro -to Helen -amount 2
Elapsed Time: 2.533 seconds 
correct hash Hex: 00000c81d541ad407a3767ad633d1147602df86fe14e1962ec145ab17b633e88 

Success!


$ java -jar blockchain-java-jar-with-dependencies.jar send -from wangwei -to Helen -amount 2
Elapsed Time: 1.481 seconds 
correct hash Hex: 00000c3f8b82c2b970438f5f1f39d56bb8a9d66341efc92a02ffcbff91acd84b 

Success!

現(xiàn)在,Helen 這個錢包地址上有了兩筆從 wangwei 和 Pedro 轉(zhuǎn)賬中產(chǎn)生的UTXO,讓我們將它們再轉(zhuǎn)賬給另外一個人:

$ java -jar blockchain-java-jar-with-dependencies.jar send -from Helen -to Rachel -amount 3
Elapsed Time: 17.136 seconds 
correct hash Hex: 000000b1226a947166c2b01a15d1cd3558ddf86fe99bad28a0501a2af60f6a02 

Success!

$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address wangwei
Balance of "wangwei": 2
$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address Pedro  
Balance of "Pedro": 4
$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address Helen
Balance of "Helen": 1
$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address Rachel
Balance of "Rachel": 3

非常棒!讓我們來測試一下失敗的場景:

$ java -jar blockchain-java-jar-with-dependencies.jar send -from wangwei -to Ivan -amount 5 
java.lang.Exception: ERROR: Not enough funds
        at one.wangwei.blockchain.transaction.Transaction.newUTXOTransaction(Transaction.java:104)
        at one.wangwei.blockchain.cli.CLI.send(CLI.java:138)
        at one.wangwei.blockchain.cli.CLI.parse(CLI.java:73)
        at one.wangwei.blockchain.cli.Main.main(Main.java:7)
總結(jié)

本篇內(nèi)容有點難度,但好歹我們現(xiàn)在有了交易信息了。盡管,缺少像比特幣這一類加密貨幣的一些關(guān)鍵特性:

錢包地址。我們還沒有基于私鑰的真實地址。

獎勵。挖礦絕對沒有利潤。

UTXO集。當(dāng)我們計算錢包地址的余額時,我們需要遍歷所有的區(qū)塊中的所有交易信息,當(dāng)有許許多多的區(qū)塊時,這將花費不少的時間。此外,如果我們想驗證以后的交易,可能需要很長時間。 UTXO集旨在解決這些問題并快速處理交易。

內(nèi)存池。 這是交易在打包成區(qū)塊之前存儲的地方。 在我們當(dāng)前的實現(xiàn)中,一個塊只包含一筆交易,而且效率很低。

資料

源代碼:https://github.com/wangweiX/b...

《精通比特幣(第二版)》第6章 —— 交易

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/24017.html

相關(guān)文章

  • 基于Java語言構(gòu)建區(qū)塊)—— 交易UTXO

    摘要:交易這一環(huán)節(jié)是整個比特幣系統(tǒng)當(dāng)中最為關(guān)鍵的一環(huán),并且區(qū)塊鏈唯一的目的就是通過安全的可信的方式來存儲交易信息,防止它們創(chuàng)建之后被人惡意篡改。在比特幣中,交易輸出先于交易輸入出現(xiàn)。 showImg(https://segmentfault.com/img/remote/1460000013923562?w=3200&h=1400); 最終內(nèi)容請以原文為準(zhǔn): https://wangwei....

    ctriptech 評論0 收藏0
  • 基于Java語言構(gòu)建區(qū)塊(六)—— 交易(Merkle Tree)

    摘要:截止年月號,比特幣中有個區(qū)塊,并且這些數(shù)據(jù)占據(jù)了的磁盤空間。每個比特幣節(jié)點都是路由區(qū)塊鏈數(shù)據(jù)庫挖礦錢包服務(wù)的功能集合。是比特幣的輕量級節(jié)點,它不需要下載所有的區(qū)塊鏈數(shù)據(jù),也不需要驗證區(qū)塊和交易數(shù)據(jù)。 showImg(https://img.i7years.com/blog/pexels-photo-38136.jpeg); 最終內(nèi)容請以原文為準(zhǔn):https://wangwei.one/...

    liuhh 評論0 收藏0
  • 基于Java語言構(gòu)建區(qū)塊(六)—— 交易(Merkle Tree)

    摘要:截止年月號,比特幣中有個區(qū)塊,并且這些數(shù)據(jù)占據(jù)了的磁盤空間。每個比特幣節(jié)點都是路由區(qū)塊鏈數(shù)據(jù)庫挖礦錢包服務(wù)的功能集合。是比特幣的輕量級節(jié)點,它不需要下載所有的區(qū)塊鏈數(shù)據(jù),也不需要驗證區(qū)塊和交易數(shù)據(jù)。 showImg(https://img.i7years.com/blog/pexels-photo-38136.jpeg); 最終內(nèi)容請以原文為準(zhǔn):https://wangwei.one/...

    KevinYan 評論0 收藏0
  • 區(qū)塊開發(fā)中使用的最流行的編程語言

    摘要:我們目前正處于一個新興的區(qū)塊鏈開發(fā)行業(yè)中。,一種在以太坊開發(fā)人員中流行的新的簡單編程語言,因為它是用于開發(fā)以太坊智能合約的語言。它是全球至少萬開發(fā)人員使用的世界上最流行的編程語言之一。以太坊,主要是針對工程師使用進行區(qū)塊鏈以太坊開發(fā)的詳解。 我們目前正處于一個新興的區(qū)塊鏈開發(fā)行業(yè)中。區(qū)塊鏈技術(shù)處于初期階段,然而這種顛覆性技術(shù)已經(jīng)成功地風(fēng)靡全球,并且最近經(jīng)歷了一場與眾不同的繁榮。由于許多...

    2shou 評論0 收藏0
  • 基于Java語言構(gòu)建區(qū)塊(三)—— 持久化 & 命令行

    摘要:我們該選擇哪一款數(shù)據(jù)庫呢事實上,在比特幣白皮書中并沒有明確指定使用哪一種的數(shù)據(jù)庫,因此這個由開發(fā)人員自己決定。詳見精通比特幣第二版第章節(jié)交易的輸入與輸出此外,每個區(qū)塊數(shù)據(jù)都是以單獨的文件形式存儲在磁盤上。資料源代碼精通比特幣第二版 showImg(https://segmentfault.com/img/remote/1460000013923488?w=1200&h=627); 最...

    asoren 評論0 收藏0

發(fā)表評論

0條評論

Vultr

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<