錢包的目的是爲了給用戶建立更高層的抽象接口來對交易進行管理。node
咱們最終的目的是讓用戶能夠方便的:git
以上這些生效後,用戶就不須要知道上一章節中描述的inputs和outpus這些交易的細節,就能對交易進行管理了。就比如在比特幣網絡中,你只須要把比特幣打入對應地址就能給別人打入比特幣, 同時,你只須要將你本身的地址發佈出去,別人就能給你打比特幣了。github
本章節完整的代碼請看這裏算法
本教程中咱們將會用最簡單的方法對錢包進行初始化和存儲:將未加密的私鑰保存在node/wallet/private_key這個文件中。typescript
const privateKeyLocation = 'node/wallet/private_key'; const generatePrivatekey = (): string => { const keyPair = EC.genKeyPair(); const privateKey = keyPair.getPrivate(); return privateKey.toString(16); }; const initWallet = () => { //let's not override existing private keys if (existsSync(privateKeyLocation)) { return; } const newPrivateKey = generatePrivatekey(); writeFileSync(privateKeyLocation, newPrivateKey); console.log('new wallet with private key created'); };
如以前所言,咱們的公鑰(錢包地址)是經過私鑰演繹出來的。shell
const getPublicFromWallet = (): string => { const privateKey = getPrivateFromWallet(); const key = EC.keyFromPrivate(privateKey, 'hex'); return key.getPublic().encode('hex'); };
須要提一提的是,把私鑰明文保存在文件中是一個很是不安全的作法。咱們這樣子作只是爲了演示的簡單起見而已。同時,一個錢包當前只支持一個私鑰,因此,若是你須要一個新的公鑰來做爲地址的話,必需要建立一個新的錢包。npm
複習下上一章節提到的一個說法:你在區塊鏈中擁有的加密貨幣, 指的其實就是在「未消費交易outputs」中,接收者地址爲本身的公鑰的一系列outputs。json
這意味着,咱們若是要查看錢包餘額的話,事情就變得很是簡單了:你只須要將該地址下的全部「未消費交易output」記錄的貨幣數加起來就完了。api
const getBalance = (address: string, unspentTxOuts: UnspentTxOut[]): number => { return _(unspentTxOuts) .filter((uTxO: UnspentTxOut) => uTxO.address === address) .map((uTxO: UnspentTxOut) => uTxO.amount) .sum(); };
爲了讓演示更簡單,咱們在查詢一個錢包地址的餘額時並不須要提供任何私鑰信息。也就是說,任何人均可以查看別人的帳戶餘額。安全
進行加密貨幣交易時,用戶不該該須要關心交易中的inputs和outputs這些細枝末節。可是,當用戶A的帳戶有50個幣時,若是他要給用戶B發送10個幣時,交易後面究竟發生了什麼事情呢?
這種狀況下,系統會將10個幣發送到B的公鑰地址,同時會將剩餘的40個幣還給用戶A。也就是說,來源的50個幣必須消費完,因此在未來源的幣賦給交易的outputs時必須進行拆分。交易完後必須未來源50幣的output在「未消費交易outputs」中刪除,將新產生的兩個outputs加上去。 也就是說「未消費交易outputs」上的貨幣總量不會變,只是有些幣被交易了,地址屬於不一樣的用戶了而已。
下圖演示了上面所說起交易:
下面咱們來看一個更復雜點的場景:
這種狀況下,用戶C的全部3個outputs(在「未消費交易outputs」中address爲C公鑰的那些outputs)都爲被用上才能湊夠55個幣給D,剩餘的5個幣則會還給C。
那麼如何將以上的描述用代碼邏輯來表示呢? 首先,咱們會爲交易建立相應的inputs。怎麼建立呢?咱們會遍歷「未消費交易outputs」中地址爲發送者公鑰的項,直到找到可以湊夠足夠的幣數(outputs的幣數加起來大於等於目標幣數)的outputs。
const findTxOutsForAmount = (amount: number, myUnspentTxOuts: UnspentTxOut[]) => { let currentAmount = 0; const includedUnspentTxOuts = []; for (const myUnspentTxOut of myUnspentTxOuts) { includedUnspentTxOuts.push(myUnspentTxOut); currentAmount = currentAmount + myUnspentTxOut.amount; if (currentAmount >= amount) { const leftOverAmount = currentAmount - amount; return {includedUnspentTxOuts, leftOverAmount} } } throw Error('not enough coins to send transaction'); };
如代碼所示,咱們除了找到知足條件的那些未消費的交易outputs,還會記錄下交易後剩餘的須要還給發送者的幣數leftOverAmount。
找到這些未消費的交易outputs以後,咱們就能夠爲當前交易建立對應的inputs了:
const toUnsignedTxIn = (unspentTxOut: UnspentTxOut) => { const txIn: TxIn = new TxIn(); txIn.txOutId = unspentTxOut.txOutId; txIn.txOutIndex = unspentTxOut.txOutIndex; return txIn; }; const {includedUnspentTxOuts, leftOverAmount} = findTxOutsForAmount(amount, myUnspentTxouts); const unsignedTxIns: TxIn[] = includedUnspentTxOuts.map(toUnsignedTxIn);
作法很簡單,主要就是將每一個input中的txOutId指向上面找到的對應的未消費交易output項目。
緊跟着就須要建立示例中的2個outputs了:一個output是給接收者的,另一個output是發送者本身的,由於須要把剩餘的幣數還回來。固然,若是inputs中指向的幣數總和恰好等於交易量,即leftOverAmount爲0, 咱們就只須要建立一個發送給目標用戶的output就夠了。
const createTxOuts = (receiverAddress:string, myAddress:string, amount, leftOverAmount: number) => { const txOut1: TxOut = new TxOut(receiverAddress, amount); if (leftOverAmount === 0) { return [txOut1] } else { const leftOverTx = new TxOut(myAddress, leftOverAmount); return [txOut1, leftOverTx]; } };
最後,咱們會進行交易id計算(對交易內容作哈希),並對交易進行簽名,最終將交易簽名賦予給每一個input:
const tx: Transaction = new Transaction(); tx.txIns = unsignedTxIns; tx.txOuts = createTxOuts(receiverAddress, myAddress, amount, leftOverAmount); tx.id = getTransactionId(tx); tx.txIns = tx.txIns.map((txIn: TxIn, index: number) => { txIn.signature = signTxIn(tx, index, privateKey, unspentTxOuts); return txIn; });
咱們會提供'/mineTransaction‘這個api來讓用戶方便的使用錢包這個功能:
app.post('/mineTransaction', (req, res) => { const address = req.body.address; const amount = req.body.amount; const resp = generatenextBlockWithTransaction(address, amount); res.send(resp); });
如代碼所示,使用者只須要提供接收方的地址和交易數量就能經過該api來實現交易。該api調用後爲首先進行一次挖礦,得到一筆50個幣的原始交易,而後根據接收方地址和交易數量完成指定交易,最終將這些交易記錄到新增長的區塊中,同時更新咱們的「未消費交易outputs」。
爲了方便測試,本人對package.json中的啓動腳本作了些修改, 讓咱們能夠快速的啓動兩個節點進行測試,而不須要每次啓動都輸入一堆的參數。
npm run node1 npm run node 2
同時, 在啓動第二個節點時,加入PEER參數,讓第二個節點自動和節點1創建P2P鏈接。
"scripts": { "prestart": "npm run compile", "node1": "HTTP_PORT=3001 P2P_PORT=6001 WALLET=1 npm start ", "node2": "HTTP_PORT=3002 P2P_PORT=6002 WALLET=2 PEER=ws://localhost:6001 npm start ", "start": "node src/main.js", "compile": "tsc" },
最後,經過新加的WALLET參數的支持,咱們會爲每一個節點自動初始化一個錢包,這樣咱們就更容易觀察錢包和交易的行爲。
由於「未消費交易outputs」這個清單是很是重要的,這時咱們進行交易的基礎。因此頗有必要提供一個額外的接口來查看裏面的數據的變化。你能夠經過GET的方式發送請求到 "http://localhost:3001/unspentTransactionOutputs" 接口來得到該清單。
注意,若是你起了兩個節點,那麼端口使用3001和3002都是能夠的。 由於該清單是一個分佈式清單,和咱們的區塊鏈同樣,是全網同步的。
有了這些以後,你就能夠方便的經過postman調用相應的接口對錢包和交易功能進行體驗了。
咱們剛剛實現了一個未加密的錢包功能以進行簡單的交易。雖然這個交易算法(mineTransaction接口相關的邏輯)中最多隻能有兩個接收者outputs(一個是接收者,一個是本身), 可是咱們底層的區塊連接口是能支持任意數量的outputs的。好比你能夠建立一個inputs是50個幣,outputs分別是5,15和30個幣的交易, 但你必須手動填充這些數據並調用/mineRawBlock這個接口來達成。
迄今爲止,咱們進行一次交易,仍是須要先進行一次挖礦,才能將交易信息加入到新的區塊裏面。咱們當前的各個節點不會對未記錄在區塊中的交易作任何信息交換。 這些問題都將會在下一個章節進行解決。
本章節完整的代碼請看這裏
本文由天地會珠海分舵編譯,轉載需受權,喜歡點個贊,吐槽請評論,如能給Github上的項目給個星,將不勝感激.